25% off ProSNIPC25OFF

Multi-Option Poll / Survey

Shortcode-based front-end vote block that tallies votes via AJAX and shows live result percentages.

Module3 parts · by SnipCraft

PHPLogic

php
<?php
if ( ! function_exists( 'scseed_poll_shortcode' ) ) {
    function scseed_poll_shortcode( $atts ) {
        $atts = shortcode_atts( array(
            'id'       => 'poll1',
            'question' => 'What is your favourite feature?',
            'options'  => 'Option A,Option B,Option C',
        ), $atts, 'simple_poll' );

        $poll_id  = preg_replace( '/[^a-z0-9_-]/', '', strtolower( $atts['id'] ) );
        $question = esc_html( $atts['question'] );
        $options  = array_map( 'trim', explode( ',', $atts['options'] ) );
        $options  = array_slice( $options, 0, 10 );

        $voted    = isset( $_COOKIE[ 'scseed_poll_' . $poll_id ] );
        $counts   = array();
        $total    = 0;
        foreach ( $options as $i => $opt ) {
            $cnt        = (int) get_option( 'scseed_poll_' . $poll_id . '_' . $i, 0 );
            $counts[]   = $cnt;
            $total     += $cnt;
        }

        ob_start();
        ?>
        <div class="scseed-poll-wrap" data-id="<?php echo esc_attr( $poll_id ); ?>" data-nonce="<?php echo esc_attr( wp_create_nonce( 'scseed_poll' ) ); ?>" data-ajax="<?php echo esc_url( admin_url( 'admin-ajax.php' ) ); ?>">
            <p class="scseed-poll-question"><?php echo $question; ?></p>
            <?php if ( $voted ) : ?>
                <div class="scseed-poll-results" data-total="<?php echo esc_attr( $total ); ?>">
                    <?php foreach ( $options as $i => $opt ) :
                        $pct = $total > 0 ? round( ( $counts[ $i ] / $total ) * 100 ) : 0;
                    ?>
                        <div class="scseed-poll-bar-row">
                            <span class="scseed-poll-opt-label"><?php echo esc_html( $opt ); ?></span>
                            <div class="scseed-poll-bar-track"><div class="scseed-poll-bar" style="width:<?php echo $pct; ?>%"></div></div>
                            <span class="scseed-poll-pct"><?php echo $pct; ?>%</span>
                        </div>
                    <?php endforeach; ?>
                    <p class="scseed-poll-total"><?php echo esc_html( $total ); ?> votes</p>
                </div>
            <?php else : ?>
                <ul class="scseed-poll-options">
                    <?php foreach ( $options as $i => $opt ) : ?>
                        <li><button type="button" class="scseed-poll-choice" data-opt="<?php echo esc_attr( $i ); ?>"><?php echo esc_html( $opt ); ?></button></li>
                    <?php endforeach; ?>
                </ul>
            <?php endif; ?>
        </div>
        <?php
        return ob_get_clean();
    }
    add_shortcode( 'simple_poll', 'scseed_poll_shortcode' );
}

if ( ! function_exists( 'scseed_poll_vote' ) ) {
    function scseed_poll_vote() {
        check_ajax_referer( 'scseed_poll', 'nonce' );
        $poll_id = preg_replace( '/[^a-z0-9_-]/', '', sanitize_text_field( wp_unslash( $_POST['poll_id'] ?? '' ) ) );
        $opt_idx = absint( $_POST['opt'] ?? 0 );
        if ( ! $poll_id ) wp_send_json_error( 'invalid' );

        $key     = 'scseed_poll_' . $poll_id . '_' . $opt_idx;
        $current = (int) get_option( $key, 0 );
        update_option( $key, $current + 1, false );

        $max     = 20;
        $results = array();
        $total   = 0;
        for ( $i = 0; $i < $max; $i++ ) {
            $c = (int) get_option( 'scseed_poll_' . $poll_id . '_' . $i, 0 );
            if ( $c === 0 && $i > $opt_idx ) break;
            $results[] = $c;
            $total    += $c;
        }
        setcookie( 'scseed_poll_' . $poll_id, '1', time() + 30 * DAY_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN );
        wp_send_json_success( array( 'results' => $results, 'total' => $total ) );
    }
    add_action( 'wp_ajax_scseed_poll_vote', 'scseed_poll_vote' );
    add_action( 'wp_ajax_nopriv_scseed_poll_vote', 'scseed_poll_vote' );
}

CSSStyles

css
.scseed-poll-wrap {
  max-width: 480px;
  margin: 1.5rem auto;
  background: #fff;
  border: 1px solid #e2e8f0;
  border-radius: 10px;
  padding: 1.5rem 1.75rem;
  font-family: inherit;
}
.scseed-poll-question { font-size: 1.05rem; font-weight: 700; color: #111827; margin: 0 0 1rem; }
.scseed-poll-options { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: .5rem; }
.scseed-poll-choice {
  width: 100%;
  text-align: left;
  background: #f3f4f6;
  border: 2px solid transparent;
  border-radius: 8px;
  padding: .65rem 1rem;
  cursor: pointer;
  font-size: .95rem;
  transition: border-color .15s, background .15s;
}
.scseed-poll-choice:hover { border-color: #6366f1; background: #eff6ff; }
.scseed-poll-bar-row { display: flex; align-items: center; gap: .6rem; margin-bottom: .6rem; }
.scseed-poll-opt-label { flex: 0 0 120px; font-size: .85rem; color: #374151; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.scseed-poll-bar-track { flex: 1; background: #e5e7eb; border-radius: 999px; height: 10px; overflow: hidden; }
.scseed-poll-bar { height: 100%; background: #6366f1; border-radius: 999px; transition: width .8s ease; }
.scseed-poll-pct { font-size: .8rem; font-weight: 600; color: #4b5563; min-width: 30px; text-align: right; }
.scseed-poll-total { font-size: .78rem; color: #9ca3af; margin-top: .3rem; }

JavaScriptScript

js
(function () {
  document.querySelectorAll('.scseed-poll-wrap').forEach(function (wrap) {
    wrap.querySelectorAll('.scseed-poll-choice').forEach(function (btn) {
      btn.addEventListener('click', function () {
        var pollId = wrap.dataset.id;
        var opt    = btn.dataset.opt;
        var nonce  = wrap.dataset.nonce;
        var ajax   = wrap.dataset.ajax;

        wrap.querySelectorAll('.scseed-poll-choice').forEach(function (b) { b.disabled = true; });

        var body = new URLSearchParams();
        body.append('action', 'scseed_poll_vote');
        body.append('poll_id', pollId);
        body.append('opt', opt);
        body.append('nonce', nonce);

        fetch(ajax, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: body.toString() })
          .then(function (r) { return r.json(); })
          .then(function (data) {
            if (!data.success) return;
            var results = data.data.results;
            var total   = data.data.total;
            var opts    = wrap.querySelectorAll('.scseed-poll-choice');
            var html    = '<div class="scseed-poll-results">';
            results.forEach(function (cnt, i) {
              var pct = total > 0 ? Math.round((cnt / total) * 100) : 0;
              var label = opts[i] ? opts[i].textContent : ('Option ' + (i + 1));
              html += '<div class="scseed-poll-bar-row">' +
                '<span class="scseed-poll-opt-label">' + label + '</span>' +
                '<div class="scseed-poll-bar-track"><div class="scseed-poll-bar" style="width:' + pct + '%"></div></div>' +
                '<span class="scseed-poll-pct">' + pct + '%</span></div>';
            });
            html += '<p class="scseed-poll-total">' + total + ' votes</p></div>';
            wrap.querySelector('.scseed-poll-options').outerHTML = html;
          });
      });
    });
  });
})();
#ajax#interactive#poll#survey#vote