Hunter  Krajcik

Hunter Krajcik

1674813120

Validate Multi Step Form Using jQuery

In this article, we will see how to validate multi step form wizard using jquery. Here, we will learn to validate the multi step form using jquery. First, we create the multi step form using bootstrap. Also, in this example, we are not using any jquery plugin for multi step form wizard.

So, let's see jquery multi step form with validation, how to create multi step form, multi step form wizard with jquery validation, bootstrap 4 multi step form wizard with validation, multi step form bootstrap 5, and jQuery multi step form with validation and next previous navigation.

Add HTML:

<html lang="en">
</head>
<link href="https://fonts.googleapis.com/css?family=Roboto:400,700&display=swap" rel="stylesheet">
</head>
<body>  
    <div class="main">
      <h3>How To Validate Multi Step Form Using jQuery - Websolutionstuff</h3>
        <form id="multistep_form">
            <!-- progressbar -->
            <ul id="progress_header">
                <li class="active"></li>
                <li></li>
                <li></li>
            </ul>
            <!-- Step 01 -->
            <div class="multistep-box">
                <div class="title-box">
                    <h2>Create your account</h2>
                </div>
                <p>
                    <input type="text" name="email" placeholder="Email" id="email">
                    <span id="error-email"></span>
                </p>
                <p>
                    <input type="password" name="pass" placeholder="Password" id="pass">
                    <span id="error-pass"></span>
                </p>
                <p>
                    <input type="password" name="cpass" placeholder="Confirm Password" id="cpass">
                    <span id="error-cpass"></span>
                </p>
                <p class="nxt-prev-button"><input type="button" name="next" class="fs_next_btn action-button" value="Next" /></p>
            </div>
            <!-- Step 02 -->
            <div class="multistep-box">
                <div class="title-box">
                    <h2>Social Profiles</h2>
                </div>
                <p>
                    <input type="text" name="twitter" placeholder="Twitter" id="twitter">
                    <span id="error-twitter"></span>
                </p>
                <p>
                    <input type="text" name="facebook" placeholder="Facebook" id="facebook">
                    <span id="error-facebook"></span>
                </p>
                <p>
                    <input type="text" name="linkedin" placeholder="Linkedin" id="linkedin">
                    <span id="error-linkedin"></span>
                </p>
                <p class="nxt-prev-button">
                    <input type="button" name="previous" class="previous action-button" value="Previous" />
                    <input type="button" name="next" class="ss_next_btn action-button" value="Next" />
                </p>
            </div>
            <!-- Step 03 -->
            <div class="multistep-box">
                <div class="title-box">
                    <h2>Personal Details</h2>
                </div>
                <p>
                    <input type="text" name="fname" placeholder="First Name" id="fname">
                    <span id="error-fname"></span>
                </p>
                <p>
                    <input type="text" name="lname" placeholder="Last Name" id="lname">
                    <span id="error-lname"></span>
                </p>
                <p>
                    <input type="text" name="phone" placeholder="Phone" id="phone">
                    <span id="error-phone"></span>
                </p>
                <p>
                    <textarea name="address" placeholder="Address" id="address"></textarea>
                    <span id="error-address"></span>
                </p>
                <p class="nxt-prev-button"><input type="button" name="previous" class="previous action-button" value="Previous" />
                    <input type="submit" name="submit" class="submit_btn ts_next_btn action-button" value="Submit" />
                </p>
            </div>
        </form>
        <h1>You are successfully logged in</h1>
    </div>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-easing/1.4.0/jquery.easing.js" type="text/javascript"></script>
</body>
</html>

Add CSS:

body {
    display: inline-block;
    width: 100%;
    height: 100vh;
    overflow: hidden;
    background-image: url("https://img1.akspic.com/image/80377-gadget-numeric_keypad-input_device-electronic_device-space_bar-3840x2160.jpg");
    background-repeat: no-repeat;
    background-size: cover;
    position: relative;
    margin: 0;
    font-weight: 400;
    font-family: 'Roboto', sans-serif;
}
body:before {
    content: "";
    display: block;
    width: 100%;
    height: 100%;
    background: rgba(0,0,0,0.5);
    position: absolute;
    left: 0;
    right: 0;
    top: 0;
    bottom: 0;
}
.main {
    position: absolute;
    left: 0;
    right: 0;
    top: 30px;
    margin: 0 auto;
    height: 515px;
}
input:-internal-autofill-selected {
    background-color: #fff !important;
}
#multistep_form {
    width: 550px;
    margin: 0 auto;
    text-align: center;
    position: relative;
    height: 100%;
    z-index: 999;
    opacity: 1; 
    visibility: visible; 
}
/*progress header*/
#progress_header {
    overflow: hidden;
    margin: 0 auto 30px;
    padding: 0;
}
#progress_header li {
    list-style-type: none;
    width: 33.33%;
    float: left;
    position: relative;
    font-size: 16px;
    font-weight: bold;
    font-family: monospace;
    color: #fff;
    text-transform: uppercase;
}
#progress_header li:after {
    width: 35px;
    line-height: 35px;
    display: block;
    font-size: 22px;
    color: #888;
    font-family: monospace;
    background-color: #fff;
    border-radius: 100px;
    margin: 0 auto;
    background-repeat: no-repeat;
    font-family: 'Roboto', sans-serif;
}
#progress_header li:nth-child(1):after {
    content: "1";
}
#progress_header li:nth-child(2):after {
    content: "2";
}
#progress_header li:nth-child(3):after {
    content: "3";
}
#progress_header li:before {
    content: '';
    width: 100%;
    height: 5px;
    background: #fff;
    position: absolute;
    left: -50%;
    top: 50%;
    z-index: -1;
}
#progress_header li:first-child:before {
    content: none;
}
#progress_header li.active:before, 
#progress_header li.active:after {
    background-image: linear-gradient(to right top, #35e8c3, #36edbb, #3df2b2, #4af7a7, #59fb9b) !important;
    color: #fff !important;
    transition: all 0.5s;
}
/*title*/
.title-box {
    width: 100%;
    margin: 0 0 30px 0;
}
.title-box h2 {
    font-size: 22px;
    text-transform: uppercase;
    color: #2C3E50;
    margin: 0;
    font-family: cursive;
    display: inline-block;
    position: relative;
    padding: 0 0 10px 0;
    font-family: 'Roboto', sans-serif;
}
.title-box h2:before {
    content: "";
    background: #6ddc8b;
    width: 70px;
    height: 2px;
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    margin: 0 auto;
    display: block;
}
.title-box h2:after {
    content: "";
    background: #6ddc8b;
    width: 50px;
    height: 2px;
    position: absolute;
    bottom: -5px;
    left: 0;
    right: 0;
    margin: 0 auto;
    display: block;
}
/*Input and Button*/
.multistep-box {
    background: white;
    border: 0 none;
    border-radius: 3px;
    box-shadow: 1px 1px 55px 3px rgba(255, 255, 255, 0.4);
    padding: 30px 30px;
    box-sizing: border-box;
    width: 80%;
    margin: 0 10%;
    position: absolute;
}
.multistep-box:not(:first-of-type) {
    display: none;
}
.multistep-box p {
    margin: 0 0 12px 0;
    text-align: left;
}
.multistep-box span {
    font-size: 12px;
    color: #FF0000;
}
input, textarea {
    padding: 15px;
    border: 1px solid #ccc;
    border-radius: 3px;
    margin: 0;
    width: 100%;
    box-sizing: border-box;
    font-family: 'Roboto', sans-serif;
    color: #2C3E50;
    font-size: 13px;
    transition: all 0.5s;
    outline: none;
}
input:focus, textarea:focus {
    box-shadow: inset 0px 0px 50px 2px rgb(0,0,0,0.1);
}
input.box_error, textarea.box_error {
    border-color: #FF0000;
    box-shadow: inset 0px 0px 50px 2px rgb(255,0,0,0.1);
}
input.box_error:focus, textarea.box_error:focus {
    box-shadow: inset 0px 0px 50px 2px rgb(255,0,0,0.1);
}
p.nxt-prev-button {
    margin: 25px 0 0 0;
    text-align: center;
}
.action-button {
    width: 100px;
    font-weight: bold;
    color: white;
    border: 0 none;
    border-radius: 1px;
    cursor: pointer;
    padding: 10px 5px;
    margin: 0 5px;
    background-image: linear-gradient(to right top, #35e8c3, #36edbb, #3df2b2, #4af7a7, #59fb9b);
    transition: all 0.5s;
}
.action-button:hover, 
.action-button:focus {
    box-shadow: 0 0 0 2px white, 0 0 0 3px #6ce199;
}
.form_submited #multistep_form {
    opacity: 0; 
    visibility: hidden; 
}
.form_submited h1 {
    -webkit-background-clip: text;
    transform: translate(0%, 0%);
    -webkit-transform: translate(0%, 0%);
    transition: all 0.3s ease;
    opacity: 1;
    visibility: visible;
}
h1 {
    margin: 0;
    text-align: center;
    font-size: 90px;
    background-image: linear-gradient(to right top, #35e8c3, #36edbb, #3df2b2, #4af7a7, #59fb9b) !important;
    background-image: linear-gradient(to right top, #35e8c3, #36edbb, #3df2b2, #4af7a7, #59fb9b) !important;
    color: transparent;
    -webkit-background-clip: text;
    -webkit-background-clip: text;
    transform: translate(0%, -80%);
    -webkit-transform: translate(0%, -80%);
    transition: all 0.3s ease;
    opacity: 0;
    visibility: hidden;
    position: absolute;
    left: 0;
    right: 0;
    margin: 0 auto;
    text-align: center;
    top: 50%;
}
h3{
  color:#fff;
  text-align:center;
  margin-bottom:20px;
}

Add jQuery:

var current_slide, next_slide, previous_slide;
var left, opacity, scale;
var animation;

var error = false;

// email validation
$("#email").keyup(function() {
    var emailReg = /^([\w-\.]+@([\w-]+\.)+[\w-]{2,4})?$/;
    if (!emailReg.test($("#email").val())) {
        $("#error-email").text('Please enter an Email addres.');
        $("#email").addClass("box_error");
        error = true;
    } else {
        $("#error-email").text('');
        error = false;
        $("#email").removeClass("box_error");
    }
});
// password validation
$("#pass").keyup(function() {
    var pass = $("#pass").val();
    var cpass = $("#cpass").val();

    if (pass != '') {
        $("#error-pass").text('');
        error = false;
        $("#pass").removeClass("box_error");
    }
    if (pass != cpass && cpass != '') {
        $("#error-cpass").text('Password and Confirm Password is not matched.');
        error = true;
    } else {
        $("#error-cpass").text('');
        error = false;
    }
});
// confirm password validation
$("#cpass").keyup(function() {
    var pass = $("#pass").val();
    var cpass = $("#cpass").val();

    if (pass != cpass) {
        $("#error-cpass").text('Please enter the same Password as above.');
        $("#cpass").addClass("box_error");
        error = true;
    } else {
        $("#error-cpass").text('');
        error = false;
        $("#cpass").removeClass("box_error");
    }
});
// twitter
$("#twitter").keyup(function() {
    var twitterReg = /https?:\/\/twitter\.com\/(#!\/)?[a-z0-9_]+$/;
    if (!twitterReg.test($("#twitter").val())) {
        $("#error-twitter").text('Twitter link is not valid.');
        $("#twitter").addClass("box_error");
        error = true;
    } else {
        $("#error-twitter").text('');
        error = false;
        $("#twitter").removeClass("box_error");
    }
});
// facebook
$("#facebook").keyup(function() {
    var facebookReg = /^(https?:\/\/)?(www\.)?facebook.com\/[a-zA-Z0-9(\.\?)?]/;
    if (!facebookReg.test($("#facebook").val())) {
        $("#error-facebook").text('Facebook link is not valid.');
        $("#facebook").addClass("box_error");
        error = true;
    } else {
        $("#error-facebook").text('');
        error = false;
        $("#facebook").removeClass("box_error");
    }
});
// linkedin
$("#linkedin").keyup(function() {
    var linkedinReg = /(ftp|http|https):\/\/?(?:www\.)?linkedin.com(\w+:{0,1}\w*@)?(\S+)(:([0-9])+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/;
    if (!linkedinReg.test($("#linkedin").val())) {
        $("#error-linkedin").text('Linkedin link is not valid.');
        $("#linkedin").addClass("box_error");
        error = true;
    } else {
        $("#error-linkedin").text('');
        error = false;
        $("#linkedin").removeClass("box_error");
    }
});
// first name
$("#fname").keyup(function() {
    var fname = $("#fname").val();
    if (fname == '') {
        $("#error-fname").text('Enter your First name.');
        $("#fname").addClass("box_error");
        error = true;
    } else {
        $("#error-fname").text('');
        error = false;
    }
    if ((fname.length <= 2) || (fname.length > 20)) {
        $("#error-fname").text("User length must be between 2 and 20 Characters.");
        $("#fname").addClass("box_error");
        error = true;
    }
    if (!isNaN(fname)) {
        $("#error-fname").text("Only Characters are allowed.");
        $("#fname").addClass("box_error");
        error = true;
    } else {
        $("#fname").removeClass("box_error");
    }
});
// last name
$("#lname").keyup(function() {
    var lname = $("#lname").val();
    if (lname != lname) {
        $("#error-lname").text('Enter your Last name.');
        $("#lname").addClass("box_error");
        error = true;
    } else {
        $("#error-lname").text('');
        error = false;
    }
    if ((lname.length <= 2) || (lname.length > 20)) {
        $("#error-lname").text("User length must be between 2 and 20 Characters.");
        $("#lname").addClass("box_error");
        error = true;
    }
    if (!isNaN(lname)) {
        $("#error-lname").text("Only Characters are allowed.");
        $("#lname").addClass("box_error");
        error = true;
    } else {
        $("#lname").removeClass("box_error");
    }
});
// phone
$("#phone").keyup(function() {
    var phone = $("#phone").val();
    if (phone != phone) {
        $("#error-phone").text('Enter your Phone number.');
        $("#phone").addClass("box_error");
        error = true;
    } else {
        $("#error-phone").text('');
        error = false;
    }
    if (phone.length != 10) {
        $("#error-phone").text("Mobile number must be of 10 Digits only.");
        $("#phone").addClass("box_error");
        error = true;
    } else {
        $("#phone").removeClass("box_error");
    }
});
// address
$("#address").keyup(function() {
    var address = $("#address").val();
    if (address != address) {
        $("#error-address").text('Enter your Address.');
        $("#address").addClass("box_error");
        error = true;
    } else {
        $("#error-address").text('');
        error = false;
        $("#address").removeClass("box_error");
    }
});

// first step validation
$(".fs_next_btn").click(function() {
    // email
    if ($("#email").val() == '') {
        $("#error-email").text('Please enter an email address.');
        $("#email").addClass("box_error");
        error = true;
    } else {
        var emailReg = /^([\w-\.]+@([\w-]+\.)+[\w-]{2,4})?$/;
        if (!emailReg.test($("#email").val())) {
            $("#error-email").text('Please insert a valid email address.');
            error = true;
        } else {
            $("#error-email").text('');
            $("#email").removeClass("box_error");
        }
    }
    // password
    if ($("#pass").val() == '') {
        $("#error-pass").text('Please enter a password.');
        $("#pass").addClass("box_error");
        error = true;
    }
    if ($("#cpass").val() == '') {
        $("#error-cpass").text('Please enter a Confirm password.');
        $("#cpass").addClass("box_error");
        error = true;
    } else {
        var pass = $("#pass").val();
        var cpass = $("#cpass").val();

        if (pass != cpass) {
            $("#error-cpass").text('Please enter the same password as above.');
            error = true;
        } else {
            $("#error-cpass").text('');
            $("#pass").removeClass("box_error");
            $("#cpass").removeClass("box_error");
        }
    }
    // animation
    if (!error) {
        if (animation) return false;
        animation = true;

        current_slide = $(this).parent().parent();
        next_slide = $(this).parent().parent().next();

        $("#progress_header li").eq($(".multistep-box").index(next_slide)).addClass("active");

        next_slide.show();
        current_slide.animate({
            opacity: 0
        }, {
            step: function(now, mx) {
                scale = 1 - (1 - now) * 0.2;
                left = (now * 50) + "%";
                opacity = 1 - now;
                current_slide.css({
                    'transform': 'scale(' + scale + ')'
                });
                next_slide.css({
                    'left': left,
                    'opacity': opacity
                });
            },
            duration: 800,
            complete: function() {
                current_slide.hide();
                animation = false;
            },
            easing: 'easeInOutBack'
        });
    }
});
// second step validation
$(".ss_next_btn").click(function() {
    // twitter
    if ($("#twitter").val() == '') {
        $("#error-twitter").text('twitter link is required.');
        $("#twitter").addClass("box_error");
        error = true;
    } else {
        var twitterReg = /https?:\/\/twitter\.com\/(#!\/)?[a-z0-9_]+$/;
        if (!twitterReg.test($("#twitter").val())) {
            $("#error-twitter").text('Twitter link is not valid.');
            error = true;
        } else {
            $("#error-twitter").text('');
            $("#twitter").removeClass("box_error");
        }
    }
    // facebook
    if ($("#facebook").val() == '') {
        $("#error-facebook").text('Facebook link is required.');
        $("#facebook").addClass("box_error");
        error = true;
    } else {
        var facebookReg = /^(https?:\/\/)?(www\.)?facebook.com\/[a-zA-Z0-9(\.\?)?]/;
        if (!facebookReg.test($("#facebook").val())) {
            $("#error-facebook").text('Facebook link is not valid.');
            error = true;
            $("#facebook").addClass("box_error");
        } else {
            $("#error-facebook").text('');
        }
    }
    // linkedin
    if ($("#linkedin").val() == '') {
        $("#error-linkedin").text('Linkedin link is required.');
        $("#linkedin").addClass("box_error");
        error = true;
    } else {
        var linkedinReg = /(ftp|http|https):\/\/?(?:www\.)?linkedin.com(\w+:{0,1}\w*@)?(\S+)(:([0-9])+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/;
        if (!linkedinReg.test($("#linkedin").val())) {
            $("#error-linkedin").text('Linkedin link is not valid.');
            error = true;
        } else {
            $("#error-linkedin").text('');
            $("#linkedin").removeClass("box_error");
        }
    }

    if (!error) {
        if (animation) return false;
        animation = true;

        current_slide = $(this).parent().parent();
        next_slide = $(this).parent().parent().next();

        $("#progress_header li").eq($(".multistep-box").index(next_slide)).addClass("active");

        next_slide.show();
        current_slide.animate({
            opacity: 0
        }, {
            step: function(now, mx) {
                scale = 1 - (1 - now) * 0.2;
                left = (now * 50) + "%";
                opacity = 1 - now;
                current_slide.css({
                    'transform': 'scale(' + scale + ')'
                });
                next_slide.css({
                    'left': left,
                    'opacity': opacity
                });
            },
            duration: 800,
            complete: function() {
                current_slide.hide();
                animation = false;
            },
            easing: 'easeInOutBack'
        });
    }

});

// third step validation
$(".ts_next_btn").click(function() {
    // first name
    if ($("#fname").val() == '') {
        $("#error-fname").text('Enter your First name.');
        $("#fname").addClass("box_error");
        error = true;
    } else {
        var fname = $("#fname").val();
        if (fname != fname) {
            $("#error-fname").text('First name is required.');
            error = true;
        } else {
            $("#error-fname").text('');
            error = false;
            $("#fname").removeClass("box_error");
        }
        if ((fname.length <= 2) || (fname.length > 20)) {
            $("#error-fname").text("User length must be between 2 and 20 Characters.");
            error = true;
        }
        if (!isNaN(fname)) {
            $("#error-fname").text("Only Characters are allowed.");
            error = true;
        } else {
            $("#fname").removeClass("box_error");
        }
    }
    // last name
    if ($("#lname").val() == '') {
        $("#error-lname").text('Enter your Last name.');
        $("#lname").addClass("box_error");
        error = true;
    } else {
        var lname = $("#lname").val();
        if (lname != lname) {
            $("#error-lname").text('Last name is required.');
            error = true;
        } else {
            $("#error-lname").text('');
            error = false;
        }
        if ((lname.length <= 2) || (lname.length > 20)) {
            $("#error-lname").text("User length must be between 2 and 20 Characters.");
            error = true;
        } 
        if (!isNaN(lname)) {
            $("#error-lname").text("Only Characters are allowed.");
            error = true;
        } else {
            $("#lname").removeClass("box_error");
        }
    }
    // phone
    if ($("#phone").val() == '') {
        $("#error-phone").text('Enter your Phone number.');
        $("#phone").addClass("box_error");
        error = true;
    } else {
        var phone = $("#phone").val();
        if (phone != phone) {
            $("#error-phone").text('Phone number is required.');
            error = true;
        } else {
            $("#error-phone").text('');
            error = false;
        }
        if (phone.length != 10) {
            $("#error-phone").text("Mobile number must be of 10 Digits only.");
            error = true;
        } else {
            $("#phone").removeClass("box_error");
        }
    }
    // address
    if ($("#address").val() == '') {
        $("#error-address").text('Enter your Address.');
        $("#address").addClass("box_error");
        error = true;
    } else {
        var address = $("#address").val();
        if (address != address) {
            $("#error-address").text('Address is required.');
            error = true;
        } else {
            $("#error-address").text('');
            $("#address").removeClass("box_error");
            error = false;
        }
    }

    if (!error) {
        if (animation) return false;
        animation = true;

        current_slide = $(this).parent().parent();
        next_slide = $(this).parent().parent().next();

        $("#progress_header li").eq($(".multistep-box").index(next_slide)).addClass("active");

        next_slide.show();
        current_slide.animate({
            opacity: 0
        }, {
            step: function(now, mx) {
                scale = 1 - (1 - now) * 0.2;
                left = (now * 50) + "%";
                opacity = 1 - now;
                current_slide.css({
                    'transform': 'scale(' + scale + ')'
                });
                next_slide.css({
                    'left': left,
                    'opacity': opacity
                });
            },
            duration: 800,
            complete: function() {
                current_slide.hide();
                animation = false;
            },
            easing: 'easeInOutBack'
        });
    }
});
// previous
$(".previous").click(function() {
    if (animation) return false;
    animation = true;

    current_slide = $(this).parent().parent();
    previous_slide = $(this).parent().parent().prev();

    $("#progress_header li").eq($(".multistep-box").index(current_slide)).removeClass("active");

    previous_slide.show();
    current_slide.animate({
        opacity: 0
    }, {
        step: function(now, mx) {
            scale = 0.8 + (1 - now) * 0.2;
            left = ((1 - now) * 50) + "%";
            opacity = 1 - now;
            current_slide.css({
                'left': left
            });
            previous_slide.css({
                'transform': 'scale(' + scale + ')',
                'opacity': opacity
            });
        },
        duration: 800,
        complete: function() {
            current_slide.hide();
            animation = false;
        },
        easing: 'easeInOutBack'
    });
});

$(".submit_btn").click(function() {
    if (!error){
        $(".main").addClass("form_submited");
    }
    return false;
})

 

Output:

how_to_validate_multi_step_form_using_jquery_output

Original article source at: https://websolutionstuff.com/

#jquery #validate #step 

Validate Multi Step Form Using jQuery
Rupert  Beatty

Rupert Beatty

1673397360

An Animatable View That Depicts Multiple Progresses Over Time

MultiProgressView

📊 MultiProgressView is an animatable view that depicts multiple progresses over time. Modeled after UIProgressView.


Examples

To run the example project, clone the repo and run the MultiProgressViewExample target.

Basic Usage

Programmatic

Add a MultiProgressView to your view hierarchy:

let progressView = MultiProgressView()
view.addSubview(progressView)

Conform your class to the MultiProgressViewDataSource protocol and set your progress view's dataSource:

progressView.dataSource = self
func numberOfSections(in progressView: MultiProgressView) -> Int
func progressView(_ progressView: MultiProgressView, viewForSection section: Int) -> ProgressViewSection

Call setProgress(section:to:) to update your view's progress:

progressView.setProgress(section: 0, to: 0.4) //animatable

Storyboards

Drag a UIView onto your view controller and set the view's class to MultiProgressView in the Identity Inspector:

IdentityInspector

Connect your progress view to your view controller with an IBOutlet:

IBOutlet

Conform your view controller to the MultiProgressViewDataSource protocol and implement the required methods:

 func numberOfSections(in progressView: MultiProgressView) -> Int
 func progressView(_ progressView: MultiProgressView, viewForSection section: Int) -> ProgressViewSection

Set your view controller as the progress view's dataSource:

DataSource

Call setProgress(section:to:) to update your view's progress:

progressView.setProgress(section: 0, to: 0.4) //animatable

Customization

MultiProgressView

Each MultiProgressView exposes the variables listed below. If using storyboards, many of these properties can be customized directly in the view's Attribute Inspector.

var cornerRadius: CGFloat = 0
var borderWidth: CGFloat = 0
var borderColor: UIColor? = .black
var lineCap: LineCapType = .square 

var trackInset: CGFloat = 0
var trackBackgroundColor: UIColor? = .clear
var trackBorderColor: UIColor? = .black
var trackBorderWidth: CGFloat = 0

var trackImageView: UIImageView

var trackTitleLabel: UILabel
var trackTitleEdgeInsets: UIEdgeInsets = .zero
var trackTitleAlignment: AlignmentType = .center

Note: To apply a corner radius (using layer.cornerRadius or the cornerRadius variable) the lineCap type must be set to .round.

ProgressViewSection

Each ProgressViewSection exposes the following variables:

var imageView: UIImageView
var titleLabel: UILabel
var titleEdgeInsets: UIEdgeInsets = .zero
var titleAlignment: AlignmentType = .center

Installation

CocoaPods

MultiProgressView is available through CocoaPods. To install it, simply add the following line to your Podfile:

pod 'MultiProgressView'

Carthage

MultiProgressView is available through Carthage. To install it, simply add the following line to your Cartfile:

github "mac-gallagher/MultiProgressView"

Swift Package Manager

MultiProgressView is available through Swift PM. To install it, simply add the package as a dependency in Package.swift:

dependencies: [
  .package(url: "https://github.com/mac-gallagher/MultiProgressView.git", from: "1.2.0"),
]

Manual

Download and drop the MultiProgressView directory into your project.

Requirements

  • iOS 9.0+
  • Xcode 10.2+
  • Swift 5.0+

Apps Using MultiProgressView

We love to hear about apps that use MultiProgressView - feel free to submit a pull request and share yours here!


Made with ❤️ by Mac Gallagher 
(Header design by Mazen Ghani)

Download Details:

Author: mac-gallagher
Source Code: https://github.com/mac-gallagher/MultiProgressView 
License: MIT license

#swift #ios #progress 

An Animatable View That Depicts Multiple Progresses Over Time
Mahoro  Trisha

Mahoro Trisha

1668152460

Angular Progress Http: A Thin Wrapper Around Angular 2+ Http Service

angular-progress-http

A thin wrapper around Angular 2+ Http service that adds ability to work with upload/download progress

npm

build info

Usage

Import HttpModule and ProgressHttpModule
import { NgModule } from "@angular/core";
import { HttpModule } from "@angular/http";
import { ProgressHttpModule } from "angular-progress-http";

@NgModule({
    imports: [
        HttpModule,
        ProgressHttpModule
    ]
})
export class AppModule {}

Inject ProgressHttp into your component and you are ready to go. See API description below for available methods.

import {Component} from "@angular/core";
import { ProgressHttp } from "angular-progress-http";

@Component({})
export class AppComponent {
    constructor(private http: ProgressHttp) {
        const form = new FormData();
        form.append("data", "someValue or file");

        this.http
            .withUploadProgressListener(progress => { console.log(`Uploading ${progress.percentage}%`); })
            .withDownloadProgressListener(progress => { console.log(`Downloading ${progress.percentage}%`); })
            .post("/fileUpload", form)
            .subscribe((response) => {
                console.log(response)
            })
    }
}

Supported Angular versions

  • Both Angular 4 and 5 are supported by the latest version
  • Angular 2 is supported in v0.5.1. Use command npm install angular-progress-http@0.5.1 to get it

Releases

For release notes please see CHANGELOG.md

API description

ProgressHttp service extends Http service provided by Angular/Http which means that you get all of the Http methods including

request(url: string | Request, options?: RequestOptionsArgs): Observable<Response>;
get(url: string, options?: RequestOptionsArgs): Observable<Response>;
post(url: string, body: any, options?: RequestOptionsArgs): Observable<Response>;

and others.

In addition it provides two methods for handling progress:

withDownloadProgressListener(listener: (progress: Progress) => void): HttpWithDownloadProgressListener;
withUploadProgressListener(listener: (progress: Progress) => void): HttpWithUploadProgressListener;

They both take callback as argument and return new instances of the service.

The interfaces returned from methods are described below:

interface HttpWithDownloadProgressListener extends Http {
    withUploadProgressListener(listener: (progress: Progress) => void): Http;
}

interface HttpWithUploadProgressListener extends Http {
    withDownloadProgressListener(listener: (progress: Progress) => void): Http;
}

Their purpose is to make libary easier to use and add compile-time checks for method calls

progressHttp //can use http api or call withUploadProgressListener or withDownloadProgressListener
    .withUploadProgressListener(progress => {}) //can use http api or call withDownloadProgressListener
    .withDownloadProgressListener(progress => {}) //here and on lines below can only use http api
    .post("/fileUpload", form)
    .subscribe((response) => {})

This restriction is used to make sure that there are now repeating calls to add progress listeners that will overwrite previously assigned handlers and may confuse developer

Calls to both methods are immutable (return new instances and do not change the internal state of the service), so you may do next things

let http1 = this.progressHttp.withUploadProgressListener(progress => { console.log("Uploading 1") });
let http2 = this.progressHttp.withUploadProgressListener(progress => { console.log("Uploading 2") });
let http3 = http1.withDownloadProgressListener(progress => { console.log("Downloading 1") });

In the code above http1 and http2 will have different upload listeners. http3 will have same upload listener as http1 and a download listener

This behavior may be useful when uploading multiple files simultaneously e.g.

this.files.forEach(f => {
    const form = new FormData();
    form.append("file", f.file);

    this.progressHttp
        .withUploadProgressListener(progress => { f.percentage = progress.percentage; })
        .post("/fileUpload", form)
        .subscribe((r) => {
            f.uploaded = true;
        })
});

Progress interface

Both upload and download progress listeners accept single argument that implements Progress interface

interface Progress {
    event: ProgressEvent, //event emitted by XHR
    lengthComputable: boolean, //if false percentage and total are undefined
    percentage?: number, //percentage of work finished
    loaded: number, //amount of data loaded in bytes
    total?: number // amount of data total in bytes
}

How it works internally

The library tries to rely on Angular code as much as possible instead of reinventing the wheel.

It extends BrowserXhr class with logic that adds event listeners to XMLHttpRequest and executes progress listeners. Other parts that are responsible for http calls (Http, XhrConnection, XhrBackend) are used as is, which means that angular-progress-http will automatically receive fixes and new features from newer versions of angular/http

Using custom HTTP implementations

If you want to use custom Http service with progress you need to follow certain steps. Let's review them on example of ng2-adal library - a library for accessing APIs restricted by Azure AD.

  1. create factory class that will implement HttpFactory interface
interface HttpFactory {
    create<T extends Http>(backend: ConnectionBackend, requestOptions: RequestOptions): T;
}

This interface contains single method to create instances of class derived from Http. The create method accepts ConnectionBackend and default RequestOptions which are always required for Http to make creation of factory easier.

Let's examine AuthHttp (Http implementation from ng2-adal) constructor to understand what dependencies it has:

constructor(http: Http, adalService: AdalService);

As you can see, it needs an instance of http service and adalService to work properly. With this knowledge we can now create the factory class.

The factory for ng2-adal is quite simple and will look next way:

import { Injectable } from "@angular/core";
import { ConnectionBackend, RequestOptions } from "@angular/http";
import { AuthHttp, AdalService } from "ng2-adal/core";
import { HttpFactory, AngularHttpFactory } from "angular-progress-http";

@Injectable()
export class AuthHttpFactory implements HttpFactory {
  constructor(
    private adalService: AdalService,
    private angularHttpFactory: AngularHttpFactory
  ) {}

  public create(backend: ConnectionBackend, requestOptions: RequestOptions) {
    const http = this.angularHttpFactory.create(backend, requestOptions);
    return new AuthHttp(http, this.adalService);
  }
}
  1. Register created factory as a provider in your application
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpModule } from '@angular/http';
import { ProgressHttpModule, HTTP_FACTORY } from 'angular-progress-http';
import { AuthHttpModule } from "ng2-adal/core";
import { AuthHttpFactory } from "./ng2-adal.http.factory.service";

import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    HttpModule,
    ProgressHttpModule,
    AuthHttpModule
  ],
  providers: [
    { provide: HTTP_FACTORY, useClass: AuthHttpFactory }
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

That's it. Now each time when you will call methods of ProgressHttp it will use your custom http implementation internally and add progress listeners to it.

Building from sources

  1. Clone the repository to the local PC
  2. Run

npm install npm run build

  1. The built library can be found in "build" folder

Running tests

  1. Clone the repository to the local PC
  2. Run

npm install npm test

Running examples

There are two example projects at the moment

  • basic example of upload progress functionality (examples/upload-download)
  • an example that uses custom http implementation (examples/custom-http)
  1. Make sure that you built library from sources as described above
  2. Navigate to selected example folder
  3. Run

npm install npm start

  1. Open browser on http://localhost:3000
  2. Choose some files (big size of the files will let you see the progress bar) and click upload
  3. Use throttling in Chrome dev tools to slow down network if progress jumps from 0 to 100 immediately

Сontribution

Feel free to ask questions and post bugs/ideas in the issues, as well as send pull requests.


Download Details:

Author: DarkXaHTeP
Source Code: https://github.com/DarkXaHTeP/angular-progress-http

License: MIT license

#angular #progress 

Angular Progress Http: A Thin Wrapper Around Angular 2+ Http Service
Mahoro  Trisha

Mahoro Trisha

1668042780

Jq Ajax Progress: JQuery Plugin That Adds Support Of progress Promise

jQuery Ajax Progress

Lightweight jQuery plugin that adds support of progress and uploadProgress promises to $.ajax()

Installation

npm

npm install jq-ajax-progress

manually

git clone git@github.com:likerRr/jq-ajax-progress.git

How to use

Include src/jq-ajax-progress.min.js inside your html after jQuery script

Basic usage

$.ajax(url, {
    progress: function(e) {
      // track downloading
    },
    uploadProgress: function(e) {
      // track uploading
      // if (e.lengthComputable) {
      //   var completedPercentage = Math.round((e.loaded * 100) / e.total);
      //   console.log(completedPercentage);
      //}
    }
  })

Extended usage

When you have to send a chunked data to client in some cases it would be good to track what part have just received. For this purposes use boolean option chunking (false by default). If it's set as true, then the second parameter in callback function will be a chunk part.

By default all chunked response contains whole text response that already received and you should manually cut it if you need to do something with parts. One of a possible case when you send big text or media response from server to client and you don't want your client wait for whole response.

Keep in mind, that under the hood whole response is being cut from the beginning (from zero index) until last part's occurrence, so big amount of data (theoretically) may cause a performance troubles. But... just keep in mind :)

$.ajax(url, {
    chunking: true,
    progress: function(e, part) {
      console.log(part);
    }
  });

Build

Install node

Run npm install && npm run build

This will minify library and put it inside src folder

MIT LICENSE


Download Details:

Author: likerRr
Source Code: https://github.com/likerRr/jq-ajax-progress

License: MIT license

#jquery #ajax #progress 

Jq Ajax Progress: JQuery Plugin That Adds Support Of progress Promise
Nat  Grady

Nat Grady

1668026760

Progress Bar in Your R Terminal

Progress

Progress bar in your R terminal

An R package to show ASCII progress bars. Heavily influenced by the https://github.com/tj/node-progress JavaScript project.

Installation

Install the package from CRAN:

install.packages("progress")

Usage

Use the progress_bar R6 class:

library(progress)
pb <- progress_bar$new(total = 100)
for (i in 1:100) {
  pb$tick()
  Sys.sleep(1 / 100)
}
[==========================================================-------------]  81%

The progress bar is displayed after the first tick command. This might not be desirable for long computations, because nothing is shown before the first tick. It is good practice to call tick(0) at the beginning of the computation or download, which shows the progress bar immediately.

pb <- progress_bar$new(total = 100)
f <- function() {
  pb$tick(0)
  Sys.sleep(3)
  for (i in 1:100) {
    pb$tick()
    Sys.sleep(1 / 100)
  }
}
f()

Custom format, with estimated time of completion:

pb <- progress_bar$new(
  format = "  downloading [:bar] :percent eta: :eta",
  total = 100, clear = FALSE, width= 60)
for (i in 1:100) {
  pb$tick()
  Sys.sleep(1 / 100)
}
  downloading [========----------------------]  28% eta:  1s

With elapsed time:

pb <- progress_bar$new(
  format = "  downloading [:bar] :percent in :elapsed",
  total = 100, clear = FALSE, width= 60)
for (i in 1:100) {
  pb$tick()
  Sys.sleep(1 / 100)
}
  downloading [==========================------]  80% in  1s
pb <- progress_bar$new(
  format = "  downloading [:bar] :elapsedfull",
  total = 1000, clear = FALSE, width= 60)
for (i in 1:1000) {
  pb$tick()
  Sys.sleep(1 / 100)
}
  downloading [=====================--------------] 00:00:08

With number of number of ticks/total:

total <- 1000
pb <- progress_bar$new(format = "[:bar] :current/:total (:percent)", total = total)
f <- function() {
  pb$tick(0)
  Sys.sleep(3)
  for (i in 1:total) {
    pb$tick(1)
    Sys.sleep(1 / 100)
  }
}
f()
[============================-------------------------------------------------] 370/1000 ( 37%)

With custom tokens:

pb <- progress_bar$new(
  format = "  downloading :what [:bar] :percent eta: :eta",
  clear = FALSE, total = 200, width = 60)
f <- function() {
  for (i in 1:100) {
    pb$tick(tokens = list(what = "foo   "))
    Sys.sleep(2 / 100)
  }
  for (i in 1:100) {
    pb$tick(tokens = list(what = "foobar"))
    Sys.sleep(2 / 100)
  }
}
f()
  downloading foo    [======------------------]  27% eta:  4s

It can show download rates for files with unknown sizes:

pb <- progress_bar$new(
  format = "  downloading foobar at :rate, got :bytes in :elapsed",
  clear = FALSE, total = 1e7, width = 60)
f <- function() {
  for (i in 1:100) {
    pb$tick(sample(1:100 * 1000, 1))
    Sys.sleep(2/100)
  }
  pb$tick(1e7)
  invisible()
}
f()
  downloading foobar at 5.42 MB/s, got 15.45 MB in  3s

Progress bars can also digress, by supplying negative values to tick():

pb <- progress_bar$new()
f <- function() {
  pb$tick(50)  ; Sys.sleep(1)
  pb$tick(-20) ; Sys.sleep(1)
  pb$tick(50)  ; Sys.sleep(1)
  pb$tick(-30) ; Sys.sleep(1)
  pb$tick(100)
}
f()

See the manual for details and other options.

Usage with purrr iterators

If you prefer to do your iterative tasks using the purrr family of functional programming tools, rather than with for loops, there are two straightforward ways to add progress bars:

Increment the ticks in-line when calling the purrr iterator.

Define the task and increment the ticks in a separate wrapper function.

Option 1 is concise for simple one-line tasks (e.g. requiring only a single function call), while Option 2 is probably preferred for more complex multi-line tasks.

# Option 1
pb <- progress_bar$new(total = 100)
purrr::walk(1:100, ~{pb$tick(); Sys.sleep(0.1)})
[================================================>------]  89%
# Option 2
pb <- progress_bar$new(total = 100)

foo <- function(x){
  pb$tick()
  Sys.sleep(0.1)
}

purrr::walk(1:100, foo)
[==================>------------------------------------]  34%

Creating a plyr compatible progress bar

It is easy to create progress bars for plyr:

progress_progress <- function(...) {
  pb <- NULL
  list(
    init = function(x, ...) {
      pb <<- progress_bar$new(total = x, ...)
    },
    step = function() {
      pb$tick()
    },
    term = function() NULL
  )
}

You can try it with

plyr::l_ply(
  1:100,
  .fun = function(...) Sys.sleep(0.01),
  .progress = 'progress'
)

C++ API

The package also provides a C++ API, that can be used with or without Rcpp. See the example package that is included within progress. Here is a short excerpt that shows how it works:


#include <RProgress.h>

...

RProgress::RProgress pb("Downloading [:bar] ETA: :eta");

  pb.tick(0);
  for (int i = 0; i < 100; i++) {
    usleep(2.0 / 100 * 1000000);
    pb.tick();
  }

...

The C++ API has almost the same functionality as the R API, except that it does not currently support custom tokens, custom streams, and callback functions.

Note that the C++ and the R APIs are independent and for a single progress bar you need to use either one exclusively.

Download Details:

Author: r-lib
Source Code: https://github.com/r-lib/progress 
License: View license

#r #progress #bar 

Progress Bar in Your R Terminal
Rupert  Beatty

Rupert Beatty

1667602200

Ring Progress View Similar to Activity App on Apple Watch

MKRingProgressView

Ring progress view similar to Activity app on Apple Watch

MKRingProgressView

Features

  • Progress animation
  • Customizable start/end and backdrop ring colors
  • Customizable ring width
  • Customizable progress line end style
  • Customizable shadow under progress line end
  • Progress values above 100% (or 360°) can also be displayed

Installation

CocoaPods

To install MKRingProgressView via CocoaPods, add the following line to your Podfile:

pod 'MKRingProgressView'

Carthage

To install MKRingProgressView via Carthage, add the following line to your Cartfile:

github "maxkonovalov/MKRingProgressView"

Swift Package Manager

Note: Instructions below are for using SwiftPM without the Xcode UI. It's the easiest to go to your Project Settings -> Swift Packages and add MKRingProgressView from there.

To integrate using Apple's Swift package manager, without Xcode integration, add the following as a dependency to your Package.swift:

.package(url: "https://github.com/maxkonovalov/MKRingProgressView.git", .upToNextMajor(from: "2.3.0"))

Usage

See the example Xcode project. It contains 2 targets:

  • ProgressRingExample - a simple example containing a single progress ring with adjustable parameters.
  • ActivityRingsExample - an advanced usage example replicating Activity app by Apple. It also contains additional classes for convenient grouping of 3 ring progress views together.

Interface Builder

MKRingProgressView can be set up in Interface Builder. To use it, set the custom view class to MKRingProgressView. Most of the control's parameters can be customized in Interface Builder.

Code

let ringProgressView = RingProgressView(frame: CGRect(x: 0, y: 100, width: 100, height: 100))
ringProgressView.startColor = .red
ringProgressView.endColor = .magenta
ringProgressView.ringWidth = 25
ringProgressView.progress = 0.0
view.addSubview(ringProgressView)

The progress value can be animated the same way you would normally animate any property using UIView's block-based animations:

UIView.animate(withDuration: 0.5) {
    ringProgressView.progress = 1.0
}

Performance

To achieve better performance the following options are possible:

  • Set gradientImageScale to lower values like 0.5 (defaults to 1.0)
  • Set startColor and endColor to the same value
  • Set shadowOpacity to 0.0
  • Set allowsAntialiasing to false

Requirements

  • iOS 9.0
  • tvOS 9.0

Download Details:

Author: Maxkonovalov
Source Code: https://github.com/maxkonovalov/MKRingProgressView 
License: MIT license

#swift #ios #apple #progress 

Ring Progress View Similar to Activity App on Apple Watch
Elian  Harber

Elian Harber

1667515620

PB: Console Progress Bar for Golang

Terminal progress bar for Go

Installation

go get github.com/cheggaaa/pb/v3

Documentation for v1 bar available here.

Quick start

package main

import (
    "time"

    "github.com/cheggaaa/pb/v3"
)

func main() {
    count := 100000

    // create and start new bar
    bar := pb.StartNew(count)

    // start bar from 'default' template
    // bar := pb.Default.Start(count)

    // start bar from 'simple' template
    // bar := pb.Simple.Start(count)

    // start bar from 'full' template
    // bar := pb.Full.Start(count)

    for i := 0; i < count; i++ {
        bar.Increment()
        time.Sleep(time.Millisecond)
    }

    // finish bar
    bar.Finish()
}

Result will be like this:

> go run test.go
37158 / 100000 [---------------->_______________________________] 37.16% 916 p/s

Settings

// create bar
bar := pb.New(count)

// refresh info every second (default 200ms)
bar.SetRefreshRate(time.Second)

// force set io.Writer, by default it's os.Stderr
bar.SetWriter(os.Stdout)

// bar will format numbers as bytes (B, KiB, MiB, etc)
bar.Set(pb.Bytes, true)

// bar use SI bytes prefix names (B, kB) instead of IEC (B, KiB)
bar.Set(pb.SIBytesPrefix, true)

// set custom bar template
bar.SetTemplateString(myTemplate)

// check for error after template set
if err := bar.Err(); err != nil {
    return
}

// start bar
bar.Start()

Progress bar for IO Operations

package main

import (
    "crypto/rand"
    "io"
    "io/ioutil"

    "github.com/cheggaaa/pb/v3"
)

func main() {
    var limit int64 = 1024 * 1024 * 500

    // we will copy 500 MiB from /dev/rand to /dev/null
    reader := io.LimitReader(rand.Reader, limit)
    writer := ioutil.Discard

    // start new bar
    bar := pb.Full.Start64(limit)

    // create proxy reader
    barReader := bar.NewProxyReader(reader)

    // copy from proxy reader
    io.Copy(writer, barReader)

    // finish bar
    bar.Finish()
}

Custom Progress Bar templates

Rendering based on builtin text/template package. You can use existing pb's elements or create you own.

All available elements are described in the element.go file.

All in one example:

tmpl := `{{ red "With funcs:" }} {{ bar . "<" "-" (cycle . "↖" "↗" "↘" "↙" ) "." ">"}} {{speed . | rndcolor }} {{percent .}} {{string . "my_green_string" | green}} {{string . "my_blue_string" | blue}}`

// start bar based on our template
bar := pb.ProgressBarTemplate(tmpl).Start64(limit)

// set values for string elements
bar.Set("my_green_string", "green").Set("my_blue_string", "blue")

Download Details:

Author: Cheggaaa
Source Code: https://github.com/cheggaaa/pb 
License: BSD-3-Clause license

#go #golang #terminal #progress 

PB: Console Progress Bar for Golang
Rupert  Beatty

Rupert Beatty

1667284640

A Circular Progress Bar for IOS Written in Swift

UICircularProgressRing

UICircularProgress ring is a library for rendering circular progress rings and timers.

  • Declarative: Written using SwiftUI (legacy UIKit version available), UICircularProgressRing is declarative making it easy to read, and easy to use.
  • Customizable: Designed to be used in production, all of the views are highly customizable without giving up ease of use. This allows the developer to tailor the look, feel and functionality to fit their needs.
  • Tested: Battle tested in many production applications. UICircularProgressRing is also fully unit tested as well as snapshot tested so developers can feel safe.
  • Documented: UICircularProgressRing's public API is 100% documented and its something that is enforced before any code is ever added. This is a resource that will help any new user understand how to get the most out of this library.

iOS 14+ Note

Since Apple has now added a built in ProgressView the need for this library is about to be over. My recommendation: If you can support iOS 14.0 and use the new system ProgressView then you should use that. This library will be continued to be maintained (critical bugs will be fixed, etc) but no new features are planned as we are reaching EOL for this library.

Installation

UICircularProgressRing is available in two major versions. The latest version v7.0+ or legacy versions. The legacy version is written using UIKit and requires a deployment target of iOS 8.0+ or tvOS 10.0+. The latest version is written in SwiftUI and requires iOS 13.0+, macOS 15.0+, tvOS 13.0+ or WatchOS 2.0+.

For legacy installation, follow these instructions.

Swift Package Manager

Simply add this library to your package manifest or follow instructions on adding a package dependency using Xcode here.

.package(
    url: "https://github.com/luispadron/UICircularProgressRing.git",
    .branch("master")
)

Documentation

This projects public API is 100% documented and it's something we spend a lot of time working on. Please make sure to read the documentation before opening any issues, questions, etc.

Read the documentation 📖

Usage

ProgressRing

ProgressRing is a view designed to display some kind of progress, this can be anything which is represented as a percentage in the range [0, ∞). A percentage is represented in decimal format, i.e. 0.5 is 50%. Progress may be a downloading operation, the grade percentage of a users test score, etc. A short example of using ProgressRing is shown below, for more details read the docs or play with the example app.

struct ProgressRingExample: View {
    @State var progress = RingProgress.percent(0.44)

    var body: some View {
        VStack {
            ProgressRing(
                progress: $progress,
                axis: .top,
                clockwise: true,
                outerRingStyle: .init(
                    color: .color(.gray),
                    strokeStyle: .init(lineWidth: 20)
                ),
                innerRingStyle: .init(
                    color: .color(.green),
                    strokeStyle: .init(lineWidth: 10),
                    padding: 5
                )
            )
                .animation(.easeInOut(duration: 5))
                .padding(32)
        }
    }
}

An example image of a ProgressRing view rendered with a green inner circle, a gray outer circle and at 44 percent completion.

TimerRing

TimerRing is a view designed to display time. You initialize the timer by giving it a unit of time and duration, for example: .seconds(60). This means the TimerRing will run for 60 seconds, filling up the inner ring until finally reaching 100% around the entire outer ring. A short example of using TimerRing is shown below, for more details read the docs or play with the example app.

struct TimerRingExample: View {
    @State var isPaused = false
    @State var isDone = false

    var body: some View {
        TimerRing(
            time: .minutes(1),
            delay: .seconds(0.5),
            innerRingStyle: .init(
                color: .color(.green),
                strokeStyle: .init(lineWidth: 16),
                padding: 8
            ),
            isPaused: $isTimerPaused,
            isDone: $isTimerDone
        ) { currentTime in
            Text(timeFormatter.string(from: currentTime))
                .font(.title)
                .bold()
        }
    }
}

A demo image of a timer ring view with a green inner ring, a gray outer ring and at twenty-seven seconds.

Examples

Apps Using UICircularProgressRing

Download Details:

Author: luispadron
Source Code: https://github.com/luispadron/UICircularProgressRing 
License: MIT license

#swift #ios #xcode #progress 

A Circular Progress Bar for IOS Written in Swift
Rupert  Beatty

Rupert Beatty

1666793580

A Custom Reusable Circular / Progress Slider Control for iOS App

HGCircularSlider

Example

Bedtime.gif Player.gif OClock.gif Other.gif Circular.gif

To run the example project, clone the repo, and run pod install from the Example directory first.

You also may like

  • HGPlaceholders - Nice library to show placeholders for any UITableView in your project
  • HGRippleRadarView - A beautiful radar view to show nearby users with ripple animation, fully customizable

Requirements

  • iOS 9.0+
  • Xcode 11.4

Installation

HGCircularSlider is also available through Swift Package Manager

Follow this doc.

HGCircularSlider is also available through CocoaPods. To install it, simply add the following line to your Podfile:

pod 'HGCircularSlider', '~> 2.2.1'

HGCircularSlider is also available through Carthage. To install it, simply add the following line to your Cartfile:

github "HamzaGhazouani/HGCircularSlider"

Usage

  1. Change the class of a view from UIView to CircularSlider, RangeCircularSlider or MidPointCircularSlider
  2. Programmatically:
let circularSlider = CircularSlider(frame: myFrame)
circularSlider.minimumValue = 0.0
circularSlider.maximumValue = 1.0
circularSlider.endPointValue = 0.2

OR

let circularSlider = RangeCircularSlider(frame: myFrame)
circularSlider.startThumbImage = UIImage(named: "Bedtime")
circularSlider.endThumbImage = UIImage(named: "Wake")

let dayInSeconds = 24 * 60 * 60
circularSlider.maximumValue = CGFloat(dayInSeconds)

circularSlider.startPointValue = 1 * 60 * 60
circularSlider.endPointValue = 8 * 60 * 60
circularSlider.numberOfRounds = 2 // Two rotations for full 24h range

OR

let circularSlider = MidPointCircularSlider(frame: myFrame)
circularSlider.minimumValue = 0.0
circularSlider.maximumValue = 10.0
circularSlider.distance = 1.0
circularSlider.midPointValue = 5.0

If you would like to use it like a progress view

let progressView = CircularSlider(frame: myFrame)
progressView.minimumValue = 0.0
progressView.maximumValue = 1.0
progressView.endPointValue = 0.2 // the progress 
progressView.userInteractionEnabled = false 
// to remove padding, for more details see issue #25
progressView.thumbLineWidth = 0.0
progressView.thumbRadius = 0.0

Documentation

Full documentation is available on CocoaDocs.
You can also install documentation locally using jazzy.

References

The UI examples of the demo project inspired from Dribbble.

Player 
BasicExample
OClock
 

The project is Inspired by UICircularSlider

Download Details:

Author: HamzaGhazouani
Source Code: https://github.com/HamzaGhazouani/HGCircularSlider 
License: MIT license

#swift #ios #progress #slider 

A Custom Reusable Circular / Progress Slider Control for iOS App
React Dev

React Dev

1664954532

Build a React Progress Bar for a Multi-Step Form

Learn how to build a React progress bar for a multi-step form. We'll look at the progress element and how to add dynamic values to it with React to provide a progress indicator for a multi-step form.

(00:00) Intro
(00:14) Welcome
(00:25) Starter Code & Overview
(01:16) App Structure
(02:28) Form Context & Behavior
(04:36) Progress Bar component
(08:57) Check Progress Bar functionality
(09:25) Progress Bar CSS Styles
(14:52) Check Percentage Display
(15:06) Optimization Concerns

🔗 Starter Source Code: https://github.com/gitdagray/react-multi-step-form 

🔗 Completed Source Code: https://github.com/gitdagray/react-form-progress-bar 

Subscribe: https://www.youtube.com/c/DaveGrayTeachesCode/featured 

#react  #progress #javascript 

Build a React Progress Bar for a Multi-Step Form
Joshua Yates

Joshua Yates

1664183033

Create a Custom Music Player with HTML, CSS & JavaScript

In this tutorial, you'll learn how to create a custom music player with HTML, CSS and JavaScript.

The music player project might seem to be lengthy and complicated at first but I have divided the JavaScript code into 15 easy steps. You can easily create and customize the music player with these 15 steps.

  1. Create Initial References
  2. Create An Array For Song List
  3. Detect If It Is A Touch Device
  4. Format Time
  5. A Function To Set Song
  6. Function To Play Song
  7. Implement Function To Toggle Repeat Mode
  8. Function To Play Next Song
  9. Function To Pause Song
  10. Create Function To Play Previous Song
  11. Function To Toggle Shuffle Mode
  12. Adding Event Listener to Progress Bar
  13. Updating Progress Bar and Time
  14. Function To Create Playlist
  15. Function To Show & Hide Playlist

HTML:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Custom Music Player</title>
    <!-- Font Awesome Icons -->
    <link
      rel="stylesheet"
      href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.1/css/all.min.css"
      integrity="sha512-KfkfwYDsLkIlwQp6LFnl8zNdLGxu9YAA1QvwINks4PhcElQSvqcyVLLD9aMhXd13uQjoXtEKNosOWaZqXgel0g=="
      crossorigin="anonymous"
      referrerpolicy="no-referrer"
    />
    <!-- Google Fonts -->
    <link
      href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;600&family=Roboto+Mono&display=swap"
      rel="stylesheet"
    />
    <!-- Stylesheet -->
    <link rel="stylesheet" href="style.css" />
  </head>
  <body>
    <div class="music-player">
      <button id="playlist">
        <i class="fa-solid fa-angle-down"></i>
      </button>
      <img id="song-image" src="make-me-move.jpg" />
      <div class="song-details">
        <p id="song-name">Make Me Move</p>
        <p id="song-artist">Culture Code</p>
      </div>
      <div class="player-options">
        <button id="shuffle">
          <i class="fa-solid fa-shuffle"></i>
        </button>
        <button id="prev">
          <i class="fa-solid fa-backward-step"></i>
        </button>
        <button id="play">
          <i class="fa-solid fa-play"></i>
        </button>
        <button id="pause" class="hide">
          <i class="fa-solid fa-pause"></i>
        </button>
        <button id="next">
          <i class="fa-solid fa-forward-step"></i>
        </button>
        <button id="repeat">
          <i class="fa-solid fa-repeat"></i>
        </button>
      </div>
      <audio id="audio" preload="metadata"></audio>
      <div id="progress-bar">
        <div id="current-progress"></div>
      </div>
      <div class="time-container">
        <span id="current-time">0:00</span>
        <span id="max-duration">0:00</span>
      </div>
      <div id="playlist-container" class="hide">
        <button id="close-button">
          <i class="fa-solid fa-xmark"></i>
        </button>
        <ul id="playlist-songs"></ul>
      </div>
    </div>
    <!-- Script -->
    <script src="script.js"></script>
  </body>
</html>

CSS:

* {
  padding: 0;
  margin: 0;
  box-sizing: border-box;
}
body {
  height: 100vh;
  background: linear-gradient(to bottom, #2887e3 50%, #16191e 50%);
}
.music-player {
  font-size: 16px;
  width: 80vw;
  max-width: 25em;
  background-color: #ffffff;
  padding: 3em 1.8em;
  position: absolute;
  transform: translate(-50%, -50%);
  top: 50%;
  left: 50%;
  border-radius: 0.5em;
  box-shadow: 0.6em 1.2em 3em rgba(0, 0, 0, 0.25);
}
img {
  width: 100%;
  margin-top: 1.25em;
}
#playlist {
  float: right;
}
.song-details {
  font-family: "Poppins", sans-serif;
  text-align: center;
}
.song-details #song-name {
  font-size: 1.3em;
  font-weight: 600;
  letter-spacing: 0.3px;
}
.song-details #song-artist {
  font-size: 0.8em;
}
.player-options {
  display: flex;
  align-items: center;
  justify-content: space-around;
  padding: 0 1.25em;
  margin: 1.25em 0 0.6em 0;
}
.music-player button {
  border: none;
  background-color: transparent;
}
#play,
#pause {
  height: 2.5em;
  width: 2.5em;
  font-size: 1.8em;
  background-color: #2887e3;
  color: #ffffff;
  border-radius: 50%;
}
#prev,
#next {
  color: #16191e;
  font-size: 1.4em;
}
#shuffle,
#repeat {
  color: #949494;
  font-size: 1em;
}
.hide {
  display: none;
}
#progress-bar {
  position: relative;
  width: 100%;
  height: 0.3em;
  background-color: #eeeeee;
  margin: 1em 0;
  border-radius: 0.18em;
  cursor: pointer;
}
#current-progress {
  position: absolute;
  left: 0;
  top: 0;
  display: inline-block;
  height: 100%;
  width: 20%;
  background-color: #2887e3;
  border-radius: 0.18em;
}
.time-container {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-family: "Roboto Mono", monospace;
}
#playlist-container {
  background-color: #ffffff;
  position: absolute;
  width: 100%;
  height: 100%;
  transform: translate(-50%, -50%);
  top: 50%;
  left: 50%;
  border-radius: 0.6em;
  padding: 2em 1em;
  font-family: "Poppins", sans-serif;
}
#close-button {
  background-color: transparent;
  border: none;
  height: 2em;
  width: 2em;
  cursor: pointer;
  margin-left: 90%;
}
ul {
  list-style-type: none;
}
li {
  display: flex;
  align-items: center;
  margin: 1em 0;
  cursor: pointer;
}
.playlist-song-details {
  margin-left: 1em;
}
.playlist-song-details > span {
  display: block;
}
#playlist-song-artist-album {
  color: #949494;
  font-size: 0.8em;
}
button.active i {
  color: #2887e3;
}
@media screen and (max-width: 450px) {
  .music-player {
    font-size: 14px;
  }
}

.playlist-image-container {
  width: 3em;
}

Javascript:

Lastly, we implement the functionality using javascript. Once again, copy the code below and paste it into your script file. We create the music player in 15 easy steps. These steps are:

  1. Create Initial References
  2. Create An Array For Song List
  3. Detect If It Is A Touch Device
  4. Format Time
  5. A Function To Set Song
  6. Function To Play Song
  7. Implement Function To Toggle Repeat Mode
  8. Function To Play Next Song
  9. Function To Pause Song
  10. Create Function To Play Previous Song
  11. Function To Toggle Shuffle Mode
  12. Adding Event Listener to Progress Bar
  13. Updating Progress Bar and Time
  14. Function To Create Playlist
  15. Function To Show & Hide Playlist
const prevButton = document.getElementById("prev");
const nextButton = document.getElementById("next");
const repeatButton = document.getElementById("repeat");
const shuffleButton = document.getElementById("shuffle");
const audio = document.getElementById("audio");
const songImage = document.getElementById("song-image");
const songName = document.getElementById("song-name");
const songArtist = document.getElementById("song-artist");
const pauseButton = document.getElementById("pause");
const playButton = document.getElementById("play");
const playlistButton = document.getElementById("playlist");
const maxDuration = document.getElementById("max-duration");
const currentTimeRef = document.getElementById("current-time");
const progressBar = document.getElementById("progress-bar");
const playlistContainer = document.getElementById("playlist-container");
const closeButton = document.getElementById("close-button");
const playlistSongs = document.getElementById("playlist-songs");
const currentProgress = document.getElementById("current-progress");

//index for songs
let index;

//initially loop=true
let loop = true;

const songsList = [
  {
    name: "Make Me Move",
    link: "make-me-move.mp3",
    artist: "Culture Code",
    image: "make-me-move.jpg",
  },
  {
    name: "Where We Started",
    link: "where-we-started.mp3",
    artist: "Lost Sky",
    image: "where-we-started.jpg",
  },
  {
    name: "On & On",
    link: "on-on.mp3",
    artist: "Cartoon",
    image: "on-on.jpg",
  },
  {
    name: "Throne",
    link: "throne.mp3",
    artist: "Rival",
    image: "throne.jpg",
  },
  {
    name: "Need You Now",
    link: "need-you-now.mp3",
    artist: "Venemy",
    image: "need-you-now.jpg",
  },
];

//events object
let events = {
  mouse: {
    click: "click",
  },
  touch: {
    click: "touchstart",
  },
};

let deviceType = "";

//Detect touch device

const isTouchDevice = () => {
  try {
    //We try to create TouchEvent(it would fail for desktops and throw error)
    document.createEvent("TouchEvent");
    deviceType = "touch";
    return true;
  } catch (e) {
    deviceType = "mouse";
    return false;
  }
};

//Format time (convert ms to seconds, minutes and add 0 id less than 10)
const timeFormatter = (timeInput) => {
  let minute = Math.floor(timeInput / 60);
  minute = minute < 10 ? "0" + minute : minute;
  let second = Math.floor(timeInput % 60);
  second = second < 10 ? "0" + second : second;
  return `${minute}:${second}`;
};

//set song
const setSong = (arrayIndex) => {
  //this extracts all the variables from the object
  let { name, link, artist, image } = songsList[arrayIndex];
  audio.src = link;
  songName.innerHTML = name;
  songArtist.innerHTML = artist;
  songImage.src = image;
  //display duration when metadata loads
  audio.onloadedmetadata = () => {
    maxDuration.innerText = timeFormatter(audio.duration);
  };
};

//play song
const playAudio = () => {
  audio.play();
  pauseButton.classList.remove("hide");
  playButton.classList.add("hide");
};

//repeat button
repeatButton.addEventListener("click", () => {
  if (repeatButton.classList.contains("active")) {
    repeatButton.classList.remove("active");
    audio.loop = false;
    console.log("repeat off");
  } else {
    repeatButton.classList.add("active");
    audio.loop = true;
    console.log("repeat on");
  }
});

//Next song
const nextSong = () => {
  //if loop is true then continue in normal order
  if (loop) {
    if (index == songsList.length - 1) {
      //If last song is being played
      index = 0;
    } else {
      index += 1;
    }
    setSong(index);

    playAudio();
  } else {
    //else find a random index and play that song
    let randIndex = Math.floor(Math.random() * songsList.length);
    console.log(randIndex);
    setSong(randIndex);
    playAudio();
  }
};

//pause song
const pauseAudio = () => {
  audio.pause();
  pauseButton.classList.add("hide");
  playButton.classList.remove("hide");
};

//previous song ( you can't go back to a randomly played song)
const previousSong = () => {
  if (index > 0) {
    pauseAudio();
    index -= 1;
  } else {
    //if first song is being played
    index = songsList.length - 1;
  }
  setSong(index);
  playAudio();
};

//next song when current song ends
audio.onended = () => {
  nextSong();
};

//Shuffle songs
shuffleButton.addEventListener("click", () => {
  if (shuffleButton.classList.contains("active")) {
    shuffleButton.classList.remove("active");
    loop = true;
    console.log("shuffle off");
  } else {
    shuffleButton.classList.add("active");
    loop = false;
    console.log("shuffle on");
  }
});

//play button
playButton.addEventListener("click", playAudio);

//next button
nextButton.addEventListener("click", nextSong);

//pause button
pauseButton.addEventListener("click", pauseAudio);

//prev button
prevButton.addEventListener("click", previousSong);

//if user clicks on progress bar
isTouchDevice();
progressBar.addEventListener(events[deviceType].click, (event) => {
  //start of progressBar
  let coordStart = progressBar.getBoundingClientRect().left;
  //mouse click position
  let coordEnd = !isTouchDevice() ? event.clientX : event.touches[0].clientX;
  let progress = (coordEnd - coordStart) / progressBar.offsetWidth;

  //set width to progress
  currentProgress.style.width = progress * 100 + "%";

  //set time
  audio.currentTime = progress * audio.duration;

  //play
  audio.play();
  pauseButton.classList.remove("hide");
  playButton.classList.add("hide");
});

//update progress every second
setInterval(() => {
  currentTimeRef.innerHTML = timeFormatter(audio.currentTime);
  currentProgress.style.width =
    (audio.currentTime / audio.duration.toFixed(3)) * 100 + "%";
});

//update time
audio.addEventListener("timeupdate", () => {
  currentTimeRef.innerText = timeFormatter(audio.currentTime);
});

//Creates playlist
const initializePlaylist = () => {
  for (let i in songsList) {
    playlistSongs.innerHTML += `<li class='playlistSong' onclick='setSong(${i})'>
            <div class="playlist-image-container">
                <img src="${songsList[i].image}"/>
            </div>
            <div class="playlist-song-details">
                <span id="playlist-song-name">
                    ${songsList[i].name}
                </span>
                <span id="playlist-song-artist-album">
                    ${songsList[i].artist}
                </span>
            </div>
        </li>`;
  }
};

//display playlist
playlistButton.addEventListener("click", () => {
  playlistContainer.classList.remove("hide");
});

//hide playlist
closeButton.addEventListener("click", () => {
  playlistContainer.classList.add("hide");
});

window.onload = () => {
  //initially first song
  index = 0;
  setSong(index);
  //create playlist
  initializePlaylist();
};

Related Videos

Create Custom Music Player in HTML CSS & JavaScript

Build a Music Player | Vanilla JavaScript

Build a Music Player with HTML CSS & JavaScript

Create A Music Website Using HTML CSS JAVASCRIPT | Add Music In HTML Website

 

#html #css #javascript

Create a Custom Music Player with HTML, CSS & JavaScript
Royce  Reinger

Royce Reinger

1663949220

4 Best Progress Rust You Must Know

In today's post we will learn about 4 Best Progress Rust You Must Know.

What is Progress?

Progress is difficult for most people to define. It’s relatively easy to come up with indicators of progress like increased education, health status or income, but how do you characterize the nature of progress?

We tend to think of progress as moving in an upward direction, where things just keep getting better. However, human progress is much more complicated than this. What seems like progress over the short term can end up being highly destructive over the longer term. Furthermore, anytime we create new capacities or resources, we also introduce new threats – so progress is not a simple forward movement.

To understand progress, you have to understand the dynamic relationship between capacities and challenges (Figure 1: Adaptive Positioning Progress Vector, below). Progress can be defined as any path of learning and action that moves in the direction of reducing threats/missed opportunities and increasing our capacity to deal with them. That relationship is critical, because we can reduce threats or maximize opportunities in ways that diminish our capacity. (Addictions are a classic example of this: drugs or alcohol might help you get through the day but, over time, substance abuse impedes your ability to meet life challenges and robs you of many opportunities). And we can create capacities that introduce new threats. (The development of the internet, for example, didn’t simply increase our power in positive ways; it introduced a whole range of new threats).

Table of contents:

  • A8m/pb [pbr] - Console progress bar for Rust.
  • Console-rs/indicatif [indicatif] - Indicate progress to users.
  • Etienne-napoleone/spinach [spinach] - Practical spinner for Rust. 
  • FGRibreau/spinners [spinners] - 60+ elegant terminal spinners.

1 - A8m/pb [pbr]:

Console progress bar for Rust.

Console progress bar for Rust Inspired from pb, support and tested on MacOS, Linux and Windows.

Examples

Simple example

use pbr::ProgressBar;
use std::thread;

fn main() {
    let count = 1000;
    let mut pb = ProgressBar::new(count);
    pb.format("╢▌▌░╟");
    for _ in 0..count {
        pb.inc();
        thread::sleep_ms(200);
    }
    pb.finish_print("done");
}

MultiBar example. see full example here

use std::thread;
use pbr::MultiBar;
use std::time::Duration;

fn main() {
    let mut mb = MultiBar::new();
    let count = 100;
    mb.println("Application header:");

    let mut p1 = mb.create_bar(count);
    let _ = thread::spawn(move || {
        for _ in 0..count {
            p1.inc();
            thread::sleep(Duration::from_millis(100));
        }
        // notify the multibar that this bar finished.
        p1.finish();
    });

    mb.println("add a separator between the two bars");

    let mut p2 = mb.create_bar(count * 2);
    let _ = thread::spawn(move || {
        for _ in 0..count * 2 {
            p2.inc();
            thread::sleep(Duration::from_millis(100));
        }
        // notify the multibar that this bar finished.
        p2.finish();
    });

    // start listen to all bars changes.
    // this is a blocking operation, until all bars will finish.
    // to ignore blocking, you can run it in a different thread.
    mb.listen();
}

Broadcast writing (simple file copying)

#![feature(io)]
use std::io::copy;
use std::io::prelude::*;
use std::fs::File;
use pbr::{ProgressBar, Units};

fn main() {
    let mut file = File::open("/usr/share/dict/words").unwrap();
    let n_bytes = file.metadata().unwrap().len() as usize;
    let mut pb = ProgressBar::new(n_bytes);
    pb.set_units(Units::Bytes);
    let mut handle = File::create("copy-words").unwrap().broadcast(&mut pb);
    copy(&mut file, &mut handle).unwrap();
    pb.finish_print("done");
}

View on Github

2- Console-rs/indicatif [indicatif]:

Indicate progress to users.

A Rust library for indicating progress in command line applications to users.

This currently primarily provides progress bars and spinners as well as basic color support, but there are bigger plans for the future of this!

Examples

examples/yarnish.rs

 yarn.gif?raw=true

examples/download.rs

 download.gif?raw=true

examples/multi.rs

 multi-progress.gif?raw=true

examples/single.rs

single.gif?raw=true

View on Github

3 - Etienne-napoleone/spinach [spinach]:

Practical spinner for Rust. 

Install

Add as a dependency to your Cargo.toml.

[dependencies]
spinach = "2"

Usage

Basic example.

use std::thread::sleep;
use std::time::Duration;

use spinach::Spinach;

fn main() {
    let s = Spinach::new("Running task 1");
    sleep(Duration::from_secs(1));

    s.text("Running task 2");
    sleep(Duration::from_secs(1));

    s.succeed("Ran tasks successfully");
}

For general convenience, text can be passed as String or &str. When an Option, can be passed as String, &str or Option<String>

Creating

use spinach::{Color, Spinach, Spinner};

// Using defaults + custom text
let s = Spinach::new("custom text");

// Using custom spinner
let spinner = Spinner::new(vec!["▮","▯"], 80);
let s = Spinach::new_with(spinner, "custom text", Color::Red));

// Also with partial config (fallback to defaults)
let s = Spinach::new_with(None, "custom text", Color::Green);

Updating

use spinach::{Color, Spinach};

let s = Spinach::new("custom text");

// Updating text
s.text("new text");

// Updating color
s.color(Color::White);

// Updating multiple
s.update_with("new text", Color::Red);

// Also with partial update (keep current)
s.update_with(None, Color::Red);

Stopping

use spinach::{Color, Spinach};

let s = Spinach::new("custom text");

// Stop with final `✔` frame, green color and optional text change.
s.success("gg");

// Stop with final `✖` frame, red color and optional text change.
s.fail("ups");

// Stop with final `⚠` frame, yellow color and optional text change.
s.warn(None);

// Stop with final `ℹ` frame, blue color and optional text change.
s.info("notice");

// Stop current spinner (freeze the frame)
s.stop();

// Stopping with custom final frame, text and color
s.stop_with("🥬", "spinach'd", Color::Ignore);

// Also with partial update (keep current)
s.stop_with(None, None, Color::Blue);

View on Github

4 - FGRibreau/spinners [spinners]:

60+ elegant terminal spinners.

Install

See Cargo page

Usage

use spinners::{Spinner, Spinners};
use std::thread::sleep;
use std::time::Duration;

fn main() {
    let mut sp = Spinner::new(Spinners::Dots9, "Waiting for 3 seconds".into());
    sleep(Duration::from_secs(3));
    sp.stop();
}

Example

cargo run --example cycle
cargo run --example simple

View on Github

Thank you for following this article.

Related videos:

"Type-Driven API Design in Rust" by Will Crichton

#rust #progress 

4 Best Progress Rust You Must Know
Gordon  Taylor

Gordon Taylor

1660713240

NProgress: for Slim Progress Bars Like on YouTube, Medium, Etc

NProgress

Minimalist progress bar

Slim progress bars for Ajax'y applications. Inspired by Google, YouTube, and Medium.

Installation

Add nprogress.js and nprogress.css to your project.

<script src='nprogress.js'></script>
<link rel='stylesheet' href='nprogress.css'/>

NProgress is available via bower and npm.

$ npm install --save nprogress

Also available via unpkg CDN:

Basic usage

Simply call start() and done() to control the progress bar.

NProgress.start();
NProgress.done();

Turbolinks (version 5+)

Ensure you're using Turbolinks 5+, and use this: (explained here)

$(document).on('turbolinks:click', function() {
  NProgress.start();
});
$(document).on('turbolinks:render', function() {
  NProgress.done();
  NProgress.remove();
});

Turbolinks (version 3 and below)

Ensure you're using Turbolinks 1.3.0+, and use this: (explained here)

$(document).on('page:fetch',   function() { NProgress.start(); });
$(document).on('page:change',  function() { NProgress.done(); });
$(document).on('page:restore', function() { NProgress.remove(); });

Pjax

Try this: (explained here)

$(document).on('pjax:start', function() { NProgress.start(); });
$(document).on('pjax:end',   function() { NProgress.done();  });

Ideas

Add progress to your Ajax calls! Bind it to the jQuery ajaxStart and ajaxStop events.

Make a fancy loading bar even without Turbolinks/Pjax! Bind it to $(document).ready and $(window).load.

Advanced usage

Percentages: To set a progress percentage, call .set(n), where n is a number between 0..1.

NProgress.set(0.0);     // Sorta same as .start()
NProgress.set(0.4);
NProgress.set(1.0);     // Sorta same as .done()

Incrementing: To increment the progress bar, just use .inc(). This increments it with a random amount. This will never get to 100%: use it for every image load (or similar).

NProgress.inc();

If you want to increment by a specific value, you can pass that as a parameter:

NProgress.inc(0.2);    // This will get the current status value and adds 0.2 until status is 0.994

Force-done: By passing true to done(), it will show the progress bar even if it's not being shown. (The default behavior is that .done() will not do anything if .start() isn't called)

NProgress.done(true);

Get the status value: To get the status value, use .status

Configuration

minimum

Changes the minimum percentage used upon starting. (default: 0.08)

NProgress.configure({ minimum: 0.1 });

template

You can change the markup using template. To keep the progress bar working, keep an element with role='bar' in there. See the default template for reference.

NProgress.configure({
  template: "<div class='....'>...</div>"
});

easing and speed

Adjust animation settings using easing (a CSS easing string) and speed (in ms). (default: ease and 200)

NProgress.configure({ easing: 'ease', speed: 500 });

trickle

Turn off the automatic incrementing behavior by setting this to false. (default: true)

NProgress.configure({ trickle: false });

trickleSpeed

Adjust how often to trickle/increment, in ms.

NProgress.configure({ trickleSpeed: 200 });

showSpinner

Turn off loading spinner by setting it to false. (default: true)

NProgress.configure({ showSpinner: false });

parent

specify this to change the parent container. (default: body)

NProgress.configure({ parent: '#container' });

Customization

Just edit nprogress.css to your liking. Tip: you probably only want to find and replace occurrences of #29d.

The included CSS file is pretty minimal... in fact, feel free to scrap it and make your own!

Resources

Support

Bugs and requests: submit them through the project's issues tracker.
Issues

Questions: ask them at StackOverflow with the tag nprogress.
StackOverflow

Chat: join us at gitter.im.
Chat

Thanks

NProgress © 2013-2017, Rico Sta. Cruz. Released under the MIT License.
Authored and maintained by Rico Sta. Cruz with help from contributors.

ricostacruz.com  Â·  GitHub @rstacruz  Â·  Twitter @rstacruz

  

Download Details:

Author: rstacruz
Source Code: https://github.com/rstacruz/nprogress 
License: MIT license

#javascript #progress #youtube #medium 

NProgress: for Slim Progress Bars Like on YouTube, Medium, Etc

HTMX: JavaScript を使用しない Web アプリの最新のアプローチ

ドキュメントによると、HTMXライブラリを使用すると、Javascript を使用せずに最新のブラウザー機能を追加できます。CSS Transitions、AJAX、WebSockets、およびServer-Sent Eventsに HTML で直接アクセスし、属性を使用して最新のユーザー インターフェイスをすばやく構築できます。JavaScript を使用せずにクライアント側で Web アプリを開発することは可能でしょうか? このチュートリアルでは、HTMX の驚異的な機能をすべて調べます。JavaScript コードを記述せずに、Ajax リクエストの送信、ファイルのアップロード、入力の検証、CSS トランジションのトリガーの方法を学習します。

HTMX とは何ですか? なぜ重要なのですか?

2013 年、Carson Gross は代替のフロントエンド ライブラリintercooler.jsを作成しました。このライブラリには、フロントエンド Web 開発の複雑さを簡素化するためのキャッチフレーズ「Ajax With Attributes」が含まれています。新しいバージョンの intercooler.js がバージョン 2.0 になり、HTML 内で直接 Ajax、WebSockets、CSS トランジション、および Server-Sent Events にアクセスできるようにするライブラリとして記述されたhtmxになりました。

HTMX の作成者は、Web の元のモデルを使用して Web アプリを作成することにより、HTML の力を活用しようとしていると述べています。このアプローチでは、開発者は同様の機能を実現するために JavaScript を記述する必要はありません。代わりに、追加の HTML 属性を使用して、動的なコンテンツと更新を実現します。Gross 氏によると、これらが htmx の背後にある動機です。

  • なぜ HTTP リクエストのみを作成できる必要<a>が<form>あるのですか?
  • click&submitイベントだけがそれらをトリガーするのはなぜですか?
  • GET&POSTメソッドのみを使用できる必要があるのはなぜですか?
  • entireなぜスクリーンだけ交換できるのですか?

「恣意的な制約を取り除くことで、htmx は HTML をハイパーテキストとして完成させます」と彼は結論付けています。

HTMX を使用すると、開発者はプログレス バー、遅延読み込み、無限スクロール、インライン検証など、最小限の HTML とスタイリングでいくつかの UI 機能と UX パターンを実装できます。

HTMX アプローチは、Vue.js や React などの他のフロントエンド フレームワークとは異なります。クライアント側アプリケーションは JavaScript を使用してサーバーから情報を要求し、JSON 形式で受信します。HTMX では、サーバーにリクエストを送信すると、エンドポイントは完全な形式の Html を返し、ページの一部を更新します。アプリケーション ロジックはバックエンドで発生するため、HTMX を任意のサーバー側テクノロジと統合できます。

HTMX の設定

HTMX をセットアップするには、CDN 経由で読み込んで、以下のように head タグに追加します。

<script src="https://unpkg.com/htmx.org@1.7.0" integrity="sha384-EzBXYPt0/T6gxNp0nuPtLkmRpmDBbjg6WmCUZRLXBBwYYmwAUxzlSGej0ARHX0Bo" crossorigin="anonymous"></script

もう 1 つの方法は、htmx.min.js ソース ファイルをダウンロードしてプロジェクトの適切なディレクトリに追加し、次のようなスクリプト タグを使用して必要な場所に挿入することです。

<script src="/path/to/htmx.min.js"></script>

npm以下のようにしてHTMX をインストールすることもできます。

npm install htmx.org

HTMX を使用して Ajax リクエストを送信する

HTMX は、HTML から直接 AJAX リクエストを作成できるようにする一連の属性を提供します。

  • hx-post- 指定された URL に POST 要求を発行します。
  • hx-get- 指定された URL に GET リクエストを発行します。
  • hx-put- 指定された URL に PUT 要求を発行します。
  • hx-patch- 指定された URL に PATCH リクエストを発行します。
  • hx-delete- 指定された URL に DELETE リクエストを発行します。

上記の各属性は、AJAX 要求を送信する URL を受け入れます。そのため、要素がトリガーされるたびに、指定されたタイプのリクエストが指定された URL に送信されます。以下の例を考えてみましょう。

<script src="https://unpkg.com/htmx.org@1.7.0"></script>

<div
  hx-get="https://v2.jokeapi.dev/joke/Any?format=txt&safe-mode"
>Load Jokes</div>

上記のデモでは、ユーザーがLoad Jokes要素をクリックしたときにGETリクエスト ( hx-get) をジョーク API エンドポイント URL ( https://v2.jokeapi.dev/joke/Any?format=txt&safe-mode ) に送信し、 div 要素への応答。ここでは、均一で整形式のジョークを提供する REST API であるデモJoke APIを使用しました。

Htmx-デモ

コードペン リンク

次に、応答を別の HTML 要素にロードする方法を調べます。

HTMX によるリクエストのトリガー

要素の「自然な」イベントは、デフォルトで Ajax リクエストを自動的に開始します。たとえば、onchangeイベント トリガーtextarea、input、およびselect。onsubmitイベントがトリガーされている間form。イベントは、他のclickすべての要求をトリガーします。リクエストをトリガーするイベントを指定する必要がある場合、HTMX は独自の「hx-trigger」を提供します。

<script src="https://unpkg.com/htmx.org@1.7.0"></script>

<div class= "jokes" 
hx-get="https://v2.jokeapi.dev/joke/Any?format=txt&safe-mode" 
hx-trigger="mouseenter”
>
 Load Jokes
</div>

トリガー リクエスト

コードペンリンク

上記のコードの div 要素の上にマウスを置くと、提供された URL エンドポイントに GET 要求が送信され、Jokes が取得されます。

HTMX による修飾子のトリガー

前のセクションで説明したように、hx-trigger属性を変更してその動作を変更することができます。たとえば、リクエストを 1 回だけ発生させたい場合はonce、トリガーに修飾子を使用できます。

<div class= "jokes" 
hx-get="https://v2.jokeapi.dev/joke/Any?format=txt&safe-mode" 
hx-trigger="mouseenter once”
>
      Load Jokes
</div>

以下は、利用可能な修飾子のリストです。

changed- 要素の値が変更された場合にリクエストを送信します。

display:<time interval>delay-1s-要求を送信する前に、一定時間 (例: ) 待機します。

throttle: <time interval>delay-1s-要求を送信する前に、指定された時間 (例: ) 待機します。delayとは異なり、制限時間に達する前に新しいイベントが発生した場合、イベントは破棄されます。つまり、時間の終わりにリクエストがトリガーされます。

form: <css selector>- 別の要素のイベントをリッスンします。これは、キーボード ショートカットなどに使用できます。

上記のすべての属性を使用して、Active Search Box パターンなどの一般的な UX パターンを実装できます。この例では、上記の属性の動作を確認できます。

属性によるポーリングhtmx-trigger

HTMX トリガー属性を使用して、要素がnイベントの発生を待つ代わりに、指定された URL を毎秒ポーリングするように指定することもできます。

<script src="https://unpkg.com/htmx.org@1.7.0"></script>

<div class= "jokes" hx-get="https://v2.jokeapi.dev/joke/Any?format=txt&safe-mode" hx-trigger="every 3s">
      Load Jokes
</div>

ここで、HTMX はブラウザに GET リクエストを/V2.jokeapi3 秒ごとに URL に送信し、レスポンスを div 要素に表示するように指示します。

HTMX によるポーリング

コードペンリンク

リクエストインジケーター

ブラウザーはフィードバックを提供しないため、何かが起こっていることをユーザーに通知すると便利です。「htmx-indicator」クラスを使用して、HTMX でこれを行うことができます。

<div class= "jokes" hx-get="https://v2.jokeapi.dev/joke/Any?format=txt&safe-mode" >
 <button>Load Jokes</button>
 <img class="htmx-indicator" src="https://i.imgur.com/8THYvPQ.gif">
</div>

「htmx-indicator」クラスは、このクラスを持つ要素の不透明度を0デフォルトで に設定し、非表示にしますが、DOM に存在します。「ジョークの読み込み」ボタンをクリックすると、ローダー インジケーターを表示する「htmx-request」クラスが追加されます。

リクエストインジケーター

コードペンリンク

ターゲティング要素

前述のように、HTMX は AJAX 要求への応答を要求を開始した要素に読み込みます。「hx-target」属性を使用すると、リクエストを開始した要素とは異なる要素にレスポンスをロードできます。「hx-target」は CSS セレクターを受け入れ、AJAX 応答をターゲット要素に自動的にロードします。

<button class="btn" hx-get="https://v2.jokeapi.dev/joke/Any?format=txt&safe-mode" hx-target="#result" > Load Jokes
 
</button>
<div class= "jokes" id="result"></div>

上記のデモでは、ボタンをクリックすると、その下の要素load jokesに応答が自動的に読み込まれます。div

htmx_target

コードペンリンク

スワッピング

前のセクションのようhx-targetに、HTMX は Dom 内で Ajax によって返された応答をロードする方法を決定する別の方法を提供します。これを行うには、「hx-swap」属性を以下にリストされている値のいずれかで設定します。

  • innerHTML: これはデフォルト値です。リクエストを送信するターゲット要素にコンテンツを挿入します。outerHTML: ターゲット要素全体を返されたコンテンツに置き換えます。
  • afterbegin: ターゲット要素内の最初の子の前にレスポンスを追加します。
  • beforebegin: リクエストをトリガーする実際の要素の親要素としてレスポンスを先頭に追加します。
  • beforeend: リクエストを送信した要素の最後の子の後にレスポンスを追加します。
  • afterend: と同様にbeforeend、リクエストを送信する要素の後にレスポンスを追加します。
  • none: このオプションは、AJAX 要求からの応答を追加または先頭に追加しません。

上記の属性の一部を使用したスクロール プログレス バーの例を次に示します ( https://htmx.org/examples/progress-bar )。

HTMX との同期リクエスト

場合によっては、2 つの要素間で要求を同期する必要があります。ある要素からのリクエストで別の要素のリクエストをオーバーライドしたい、または他の要素のリクエストが完了するまで待機したいとします。hx-sync属性を使用してこれを行うことができます。以下のコードを検討してください。

<form hx-post="/article">
    <input id="title" name="title" type="text" 
        hx-post="/validate" 
        hx-trigger="change"
    >
    <button type="submit">Submit</button>
</form>

上記の例では、フォームの送信と個別のinput検証リクエストがあります。を使用しないhx-syncと、フォームに入力して送信すると、 と への 2 つの並列リクエストが同時にトリガー/changeさ/validateれます。ドキュメントによると、入力で使用hx-sync=" closest form:abort" すると、フォーム リクエストが存在する場合、または入力リクエストの実行中に開始された場合は、入力リクエストを監視しform、入力リクエストを停止します。

<form hx-post="/article">
    <input id="title" name="title" type="text" 
        hx-post="/validate" 
        hx-trigger="change"
        hx-sync="closest form:abort"
    >
    <button type="submit">Submit</button>
</form>

このアプローチを使用すると、2 つの要素間の同期の問題を宣言的に修正できます。hx-sync属性の詳細については、こちらをご覧ください。

HTMX を使用したファイルのアップロード

HTMX を使用すると、処理のために Ajax 経由でバックエンドに送信されるファイル アップロード フォームを作成できます。動画、画像、ドキュメントなどのファイルを簡単に送信できます。リクエストを送信する親要素にhx-encoding値を持つ属性を直接埋め込むことで、HTMX でこれを実装できます。multipart/form-data

<form hx-encoding='multipart/form-data' hx-post='/registration'
          _='on htmx:xhr:progress(loaded, total) set #progress.value to (loaded/total)*100'>
        <input type='file' name='userFile'>
        <button>
            Upload File
        </button>
   </form>

HTMX による入力検証

htmx は HTML 5 Validation API をネイティブに組み込んでいるため、検証可能な入力が無効な場合、リクエストは送信されません。この機能は、AJAX 要求と WebSockets 送信の両方に適用されます。また、HTMX は、カスタム検証とエラー処理をフックするために使用できる検証に関するイベントを発生させます。現在、次のイベントを利用できます。

  • `htmx:validation: validate – カスタム検証ロジックを追加するために使用されます。
  • htmx:validation:failed` – このイベントは、要素の検証が false を返したときに発生します。たとえば、無効な入力を示します。
  • htmx:validation: halted: 検証エラーのためにリクエストが発行されなかった場合に、このイベントが呼び出されます。オブジェクト内の特定のエラーを見つけることができevent.detail.errorsます。

入力がhyperscriptを使用しhtmx:validate:validateて値を持つことを保証するために、イベントを使用する入力を考えます。David

<form hx-post="/validate">
 <input _="on htmx:validation:validate
 if my.value != 'David'
 call me.setCustomValidity('Please enter the value David')
 else
 call me.setCustomValidity('')"
 name="username"
 >
</form>

常にバイパスできるため、すべてのクライアント側の検証はサーバー上で行う必要があることに注意することが重要です。

HTMX を使用した CSS アニメーション

Htmx を使用すると、CSS トランジションを使用して、JavaScript を使用せずに CSS と HTML のみを使用して、スムーズなアニメーションとトランジションを Web ページに追加できます。HTMX は と呼ばれる強力なアニメーション拡張機能を提供します。これにより、 or属性class-toolsを使用して要素にスワップする、または要素からスワップする CSS クラスを定義できます。classesdata-classes

classes要素に属性を割り当てることで拡張機能を使用できます。classes属性値は、& 文字で区切られた「ラン」で構成されます。実行では、指定された遅延でクラス操作を順番に適用します。これらのクラス操作にはadd、remove、 、またはtoggleCSS クラス名が付き、オプションでコロン: と時間遅延が付きます。

<div classes= “add sample-demo: 1s”></div>

sample-demoブラウザーのコンテンツが読み込まれると、HTMX は1 秒後に div に新しいクラスを自動的に追加します。以下のデモ例を見てみましょう。

<div hx-ext="class-tools">
 <div class="demo" classes="toggle faded:1s">See me Fading Away </div>

</div>

htmx_transition

CodePen-Link HTMXを使用して魅力的なアニメーションとトランジションを行う方法の詳細については、こちらを参照してください。

HTMX はさまざまなサーバー側フレームワークとシームレスに統合されますが、一部のフレームワークには HTMX をインストールするための代替手段がある場合があります。このリンクをチェックして、HTMX をさまざまなサーバー側フレームワークと統合する方法を調べてください。

結論

HTMX は信じられないほどのテクノロジであり、私はそれに興奮しており、次のプロジェクトの本番環境で使用するのが待ちきれません. このチュートリアルでは、HTMX をインストールする方法、Ajax リクエストを送信する方法、ファイルをアップロードする方法、入力を検証する方法、クライアント側で JavaScript を使用せずに CSS トランジションを作成する方法について説明しました。

このリンクをチェックして、編集してプロジェクトに統合できる HTMX で実装されたUX パターンのデモのセットを確認してください。

次のリンク ( https://htmx.org/examples/ ) には、編集してプロジェクトに統合できるUX パターンデモのセットが含まれています。

ソース: https://blog.openreplay.com/exploring-htmx-building-dynamic-web-apps-without-javascript

#javascript #htmx 

HTMX: JavaScript を使用しない Web アプリの最新のアプローチ
Anne  de Morel

Anne de Morel

1659358800

HTMX : La Nouvelle Approche Pour Les Applications Web Sans JavaScript

Selon la documentation, la bibliothèque HTMX vous permet d'ajouter des fonctionnalités de navigateur modernes sans utiliser Javascript. Il vous donne accès aux transitions CSS , AJAX , WebSockets et événements envoyés par le serveur directement en HTML, en utilisant des attributs pour créer rapidement des interfaces utilisateur modernes. Serait-il possible de développer des applications Web côté client sans JavaScript ? Dans ce didacticiel, nous allons explorer toutes les fonctionnalités époustouflantes de HTMX. Vous apprendrez à envoyer des requêtes Ajax, à télécharger des fichiers, à valider des entrées et à déclencher des transitions CSS sans écrire de code JavaScript.

Qu'est-ce que HTMX et pourquoi est-ce important ?

En 2013, Carson Gross a créé une bibliothèque frontale alternative intercooler.js , avec le slogan "Ajax With Attributes" pour simplifier la complexité du développement Web frontal. Une nouvelle version d'intercooler.js a atteint la version 2.0 et est devenue htmx décrit comme une bibliothèque qui vous permet d'accéder à Ajax, WebSockets, la transition CSS et les événements envoyés par le serveur directement dans HTML.

Le créateur de HTMX dit qu'il tente de tirer parti de la puissance de HTML en utilisant le modèle original du Web pour créer des applications Web. Avec cette approche, les développeurs n'ont pas besoin d'écrire du JavaScript pour obtenir des fonctionnalités similaires. Au lieu de cela, ils utilisent des attributs HTML supplémentaires pour obtenir un contenu et des mises à jour dynamiques. Selon Gross, ce sont les motivations derrière htmx :

  • Pourquoi ne devrait-on <a>et ne <form>peut-on que faire des requêtes HTTP ?
  • Pourquoi seuls les événements click& devraient-ils submitles déclencher ?
  • Pourquoi seules les méthodes GET& devraient-elles être disponibles ?POST
  • Pourquoi ne devriez-vous pouvoir remplacer que l' entireécran ?

« En supprimant les contraintes arbitraires, htmx complète le HTML en tant qu'hypertexte », conclut-il.

Avec HTMX, les développeurs peuvent implémenter plusieurs fonctionnalités d'interface utilisateur et modèles UX avec un minimum de code HTML et de style, tels que la barre de progression , le chargement différé , le défilement infini , la validation en ligne , etc.

L'approche HTMX diffère des autres frameworks frontaux tels que Vue.js et React, où l'application côté client utilise JavaScript pour demander des informations au serveur et les recevoir au format JSON. Avec HTMX, lorsque vous faites une demande au serveur, le point de terminaison renverra Html entièrement formé et mettra à jour une partie de la page. Vous pouvez intégrer HTMX à n'importe quelle technologie côté serveur puisque la logique d'application se produit sur le backend.

Configurer HTMX

Pour configurer HTMX, vous pouvez apparemment le charger via un CDN et l'ajouter à votre balise principale comme ceci ci-dessous :

<script src="https://unpkg.com/htmx.org@1.7.0" integrity="sha384-EzBXYPt0/T6gxNp0nuPtLkmRpmDBbjg6WmCUZRLXBBwYYmwAUxzlSGej0ARHX0Bo" crossorigin="anonymous"></script

Une autre méthode consiste à télécharger le fichier source htmx.min.js et à l'ajouter dans le répertoire approprié de votre projet, puis à l'insérer si nécessaire avec une balise de script comme celle-ci :

<script src="/path/to/htmx.min.js"></script>

Vous pouvez également installer HTMX via npmcomme ceci ci-dessous :

npm install htmx.org

Envoi de requêtes Ajax à l'aide de HTML

HTMX propose un ensemble d'attributs qui vous permettent de faire des requêtes AJAX directement à partir de HTML.

  • hx-post- envoie une requête POST à ​​l'URL donnée.
  • hx-get- envoie une requête GET à l'URL donnée.
  • hx-put- envoie une requête PUT à l'URL donnée.
  • hx-patch- envoie une requête PATCH à l'URL donnée.
  • hx-delete- envoie une requête DELETE à l'URL donnée.

Chacun de ces attributs ci-dessus accepte une URL à laquelle envoyer une requête AJAX. Ainsi, chaque fois que l'élément est déclenché, il envoie le type de requête spécifié à l'URL donnée. Considérez l'exemple ci-dessous :

<script src="https://unpkg.com/htmx.org@1.7.0"></script>

<div
  hx-get="https://v2.jokeapi.dev/joke/Any?format=txt&safe-mode"
>Load Jokes</div>

La démo ci-dessus indique au navigateur que lorsqu'un utilisateur clique sur l' Load Jokesélément, il doit envoyer une GETrequête ( hx-get) à l'URL du point de terminaison jokeapi ( https://v2.jokeapi.dev/joke/Any?format=txt&safe-mode ) et charger le réponse dans l'élément div. Nous avons utilisé ici une API Joke de démonstration , une API REST qui sert des blagues uniformément et bien formatées.

Démo Htmx

Lien Codepen

Ensuite, nous examinerons comment charger la réponse dans un autre élément HTML.

Déclencher des requêtes avec HTMX

L'événement "naturel" d'un élément lance automatiquement les requêtes Ajax par défaut. Par exemple, onchangeles déclencheurs d'événement textarea, inputet select. Pendant que l' onsubmitévénement se déclenche form. L' clickévénement déclenche toutes les autres requêtes. HTMX offre un "hx-trigger" unique si vous avez besoin de spécifier quel événement déclenchera la requête.

<script src="https://unpkg.com/htmx.org@1.7.0"></script>

<div class= "jokes" 
hx-get="https://v2.jokeapi.dev/joke/Any?format=txt&safe-mode" 
hx-trigger="mouseenter”
>
 Load Jokes
</div>

Demande de déclenchement

CodePen-Link

Lorsque la souris survole l'élément div dans le code ci-dessus, elle envoie une requête GET au point de terminaison d'URL fourni et récupère les blagues.

Déclencher des modificateurs avec HTMX

Comme mentionné dans la section précédente, il est possible de modifier l' attribut hx-trigger pour changer son comportement. Par exemple, si vous souhaitez qu'une requête ne se produise qu'une seule fois, vous pouvez utiliser le oncemodificateur pour le déclencheur.

<div class= "jokes" 
hx-get="https://v2.jokeapi.dev/joke/Any?format=txt&safe-mode" 
hx-trigger="mouseenter once”
>
      Load Jokes
</div>

Vous trouverez ci-dessous les listes des modificateurs disponibles :

changed- envoyer une requête si la valeur de l'élément a changé.

display:<time interval>- attendre un certain temps (par exemple delay-1s) avant d'envoyer la requête.

throttle: <time interval>- attendre le temps imparti (par exemple delay-1s) avant d'envoyer la requête. Contrairement à delay , si un nouvel événement se produit avant qu'il n'atteigne la limite de temps, l'événement sera rejeté, ce qui signifie qu'il déclenche une demande à la fin du temps imparti.

form: <css selector>- écouter un événement sur un élément différent. Vous pouvez l'utiliser pour des choses comme les raccourcis clavier.

Vous pouvez utiliser tous ces attributs ci-dessus pour implémenter certains modèles UX courants tels que le modèle de zone de recherche active. Avec cet exemple ici , vous pouvez voir ces attributs ci-dessus en action.

Interrogation avec l' htmx-triggerattribut

En utilisant l'attribut de déclencheur HTMX, nous pouvons également spécifier que l'élément interroge l'URL donnée toutes les nsecondes au lieu d'attendre qu'un événement se produise.

<script src="https://unpkg.com/htmx.org@1.7.0"></script>

<div class= "jokes" hx-get="https://v2.jokeapi.dev/joke/Any?format=txt&safe-mode" hx-trigger="every 3s">
      Load Jokes
</div>

Ici, le HTMX demande au navigateur d'envoyer une requête GET à l' /V2.jokeapiURL toutes les 3 secondes et d'afficher la réponse dans l'élément div.

Interrogation avec HTMX

CodePen-Link

Indicateurs de demande

Étant donné que le navigateur ne fournit aucun retour, il est utile d'informer les utilisateurs que quelque chose se passe. Vous pouvez le faire avec HTML en utilisant la classe 'htmx-indicator'.

<div class= "jokes" hx-get="https://v2.jokeapi.dev/joke/Any?format=txt&safe-mode" >
 <button>Load Jokes</button>
 <img class="htmx-indicator" src="https://i.imgur.com/8THYvPQ.gif">
</div>

La classe 'htmx-indicator' définirait l'opacité de tout élément avec cette classe 0par défaut, le rendant caché mais présent dans le DOM. Lorsque vous cliquez sur le bouton 'load jokes', il ajoute la classe 'htmx-request', qui affiche l'indicateur de chargeur.

Demande-Indicateurs

CodePen-Link

Éléments de ciblage

Comme indiqué précédemment, HTMX charge la réponse à une requête AJAX dans l'élément qui a initié la requête. L'attribut 'hx-target' permet de charger la réponse dans un élément différent de celui qui a initié la requête. Le 'hx-target' acceptera un sélecteur CSS et chargera automatiquement la réponse AJAX dans l'élément cible :

<button class="btn" hx-get="https://v2.jokeapi.dev/joke/Any?format=txt&safe-mode" hx-target="#result" > Load Jokes
 
</button>
<div class= "jokes" id="result"></div>

Dans la démo ci-dessus, lorsque vous cliquez sur le load jokesbouton, il charge automatiquement la réponse dans l' divélément en dessous.

htmx_target

CodePen-Link

Échange

Comme hx-targetdans la section précédente, HTMX offre une manière différente de déterminer comment charger la réponse renvoyée par Ajax dans le Dom. Vous pouvez le faire en définissant l'attribut 'hx-swap' avec l'une des valeurs répertoriées ci-dessous :

  • innerHTML: Ceci est la valeur par défault; il insère le contenu dans l'élément cible envoyant la requête. outerHTML: Il remplace l'intégralité de l'élément cible par le contenu renvoyé.
  • afterbegin: Il ajoute la réponse avant le premier enfant à l'intérieur de l'élément cible.
  • beforebegin: ajoute la réponse en tant qu'élément parent de l'élément réel déclenchant la requête.
  • beforeend: Il ajoute la réponse après le dernier enfant de l'élément envoyant la requête.
  • afterend: comme le beforeend, ceci ajoute la réponse après l'élément envoyant la demande.
  • none: cette option n'ajoute ni ne préfixe une réponse d'une requête AJAX.

Voici un exemple de barre de progression défilante utilisant certains des attributs mentionnés ci-dessus ( https://htmx.org/examples/progress-bar ).

Synchronisation de la requête avec HTML

Parfois, vous aurez besoin de synchroniser les requêtes entre deux éléments. Supposons que vous souhaitiez qu'une requête d'un élément remplace les requêtes d'un autre élément, ou que vous souhaitiez attendre que les requêtes de l'autre élément soient terminées. Vous pouvez le faire en utilisant l' hx-syncattribut. Considérez le code ci-dessous :

<form hx-post="/article">
    <input id="title" name="title" type="text" 
        hx-post="/validate" 
        hx-trigger="change"
    >
    <button type="submit">Submit</button>
</form>

Dans l'exemple ci-dessus, nous avons une soumission de formulaire et une inputdemande de validation individuelle. Sans utiliser hx-sync, lorsque vous remplissez le formulaire et que vous le soumettez, cela déclenche deux requêtes parallèles vers /changeet /validatesimultanément. Selon la documentation, l'utilisation hx-sync=" closest form:abort" sur l'entrée surveillera les demandes sur formet arrêtera les demandes d'entrée si une demande de formulaire est présente ou démarrera pendant que la demande d'entrée est en cours :

<form hx-post="/article">
    <input id="title" name="title" type="text" 
        hx-post="/validate" 
        hx-trigger="change"
        hx-sync="closest form:abort"
    >
    <button type="submit">Submit</button>
</form>

En utilisant cette approche, nous pouvons résoudre le problème de synchronisation entre les deux éléments de manière déclarative. Vous pouvez en savoir plus sur hx-syncl'attribut ici .

Téléchargement de fichiers avec HTML

Avec HTMX, vous pouvez créer un formulaire de téléchargement de fichier qui sera soumis via Ajax à votre backend pour traitement. Vous pouvez facilement envoyer des fichiers tels que des vidéos, des images et des documents. Vous pouvez implémenter cela avec HTMX en incorporant directement les hx-encodingattributs avec la valeur multipart/form-datadans l'élément parent envoyant la requête :

<form hx-encoding='multipart/form-data' hx-post='/registration'
          _='on htmx:xhr:progress(loaded, total) set #progress.value to (loaded/total)*100'>
        <input type='file' name='userFile'>
        <button>
            Upload File
        </button>
   </form>

Validation des entrées avec HTML

Htmx s'intègre nativement avec l'API de validation HTML 5, donc si une entrée validable n'est pas valide, elle n'enverra pas de requête. Cette fonctionnalité s'applique à la fois aux requêtes AJAX et aux envois WebSockets. En outre, HTMX déclenche des événements autour de la validation que vous pouvez utiliser pour accrocher la validation personnalisée et la gestion des erreurs. Les événements suivants sont actuellement disponibles :

  • `htmx:validation : validate - utilisé pour ajouter une logique de validation personnalisée.
  • htmx:validation:failed` - cet événement se déclenche lorsqu'une validation d'élément renvoie false, par exemple, indiquant une entrée invalide.
  • htmx:validation: halted: Il appelle cet événement lorsqu'une demande n'est pas émise en raison d'erreurs de validation. Vous pouvez trouver des erreurs spécifiques dans l' event.detail.errorsobjet.

Considérez une entrée qui utilise l' htmx:validate:validateévénement pour s'assurer que l'entrée a la valeur Daviden utilisant hyperscript :

<form hx-post="/validate">
 <input _="on htmx:validation:validate
 if my.value != 'David'
 call me.setCustomValidity('Please enter the value David')
 else
 call me.setCustomValidity('')"
 name="username"
 >
</form>

Il est important de noter que toutes les validations côté client doivent avoir lieu sur le serveur, car elles peuvent toujours être contournées.

Animations CSS avec HTML

Htmx vous permet d'utiliser des transitions CSS pour ajouter des animations et des transitions fluides à votre page Web en utilisant uniquement CSS et HTML sans JavaScript. HTMX offre une puissante extension d'animation appelée class-toolsqui vous permet de définir des classes CSS qui basculent sur ou hors de l'élément à l'aide de l' attribut classesou .data-classes

Vous pouvez utiliser l'extension en affectant l' classesattribut à un élément. La classesvaleur de l'attribut se compose de « runs » séparés par un caractère &. Dans une exécution, il appliquera les opérations de classe de manière séquentielle avec le délai spécifié. Ces opérations de classe sont add, remove, ou toggleaccompagnées d'un nom de classe CSS et éventuellement de deux-points : et d'un délai.

<div classes= “add sample-demo: 1s”></div>

Une fois le contenu du navigateur chargé, HTMX ajoutera automatiquement une nouvelle classe de sample-demoà la div après 1 seconde. Jetons un coup d'œil à cet exemple de démonstration ci-dessous :

<div hx-ext="class-tools">
 <div class="demo" classes="toggle faded:1s">See me Fading Away </div>

</div>

htmx_transition

CodePen-Link Vous pouvez en savoir plus sur la façon de créer une animation et une transition convaincantes avec HTMX ici .

HTMX s'intègre de manière transparente à différents frameworks côté serveur, bien que certains frameworks puissent proposer des alternatives pour l'installation de HTMX. Consultez ce lien pour découvrir comment vous pouvez intégrer HTMX avec différents frameworks côté serveur.

Conclusion

HTMX est une technologie incroyable, et j'en suis ravi et j'ai hâte de l'utiliser en production sur mon prochain projet. Dans ce didacticiel, nous avons exploré comment installer HTMX, envoyer des requêtes Ajax, télécharger des fichiers, valider des entrées et créer des transitions CSS sans utiliser javaScript côté client.

Consultez ce lien pour explorer certains ensembles de démos de modèles UX implémentés avec HTMX que vous pouvez modifier et intégrer à vos projets.

Le lien suivant ( https://htmx.org/examples/ ) contient des ensembles de démos de modèles UX que vous pouvez modifier et intégrer à vos projets.

Source : https://blog.openreplay.com/exploring-htmx-building-dynamic-web-apps-without-javascript

#javascript #htmx 

HTMX : La Nouvelle Approche Pour Les Applications Web Sans JavaScript