Skip to content

Per-User Post Limit & jQuery-Free My Notes

Ringkasan

Video ini membahas dua topik: (1) Membatasi jumlah note yang bisa dibuat per-user menggunakan wp_insert_post_data filter dan count_user_posts(), serta menampilkan jumlah note via REST API field kustom. (2) Menulis ulang fitur My Notes tanpa jQuery menggunakan vanilla JavaScript dan Axios.


BAGIAN 1: Per-User Post Limit

1. Batasi Jumlah Note

Kita ingin membatasi agar setiap user hanya bisa membuat maksimal 5 note.

Update wp_insert_post_data Filter:

php
add_filter('wp_insert_post_data', 'makeNotePrivate', 10, 2);

function makeNotePrivate($data, $postArray) {
  if ($data['post_type'] == 'note') {

    // Blokir jika user sudah punya 5+ note DAN ini adalah POST BARU (bukan update)
    if (count_user_posts(get_current_user_id(), 'note') > 4 AND !$postArray['ID']) {
      die("You have reached your note limit.");
    }

    if ($data['post_status'] != 'trash') {
      $data['post_status'] = 'private';
    }

    $data['post_title'] = sanitize_text_field($data['post_title']);
    $data['post_content'] = sanitize_textarea_field($data['post_content']);
  }

  return $data;
}

Penjelasan Key Concepts:

count_user_posts(user_id, post_type)

  • Menghitung jumlah post yang dimiliki user tertentu
  • Contoh: count_user_posts(get_current_user_id(), 'note') → mengembalikan angka (misal 3)

$postArray['ID'] — Membedakan Create vs Update

  • Create baru: $postArray['ID'] = 0 (falsy) → !$postArray['ID'] = true
  • Update existing: $postArray['ID'] = 142 (truthy) → !$postArray['ID'] = false
  • Ini penting karena kita hanya ingin memblokir pembuatan note baru, bukan update/delete

add_filter('wp_insert_post_data', 'makeNotePrivate', 10, 2)

ArgumenNilaiPenjelasan
1. Hook name'wp_insert_post_data'Filter hook
2. Callback'makeNotePrivate'Fungsi yang dipanggil
3. Priority10Urutan eksekusi (default)
4. Accepted args2Jumlah parameter yang diterima fungsi ($data + $postArray)

Penting: Argumen ke-4 (2) wajib karena default-nya adalah 1. Tanpa ini, parameter $postArray tidak akan dikirim ke fungsi kita.


2. Expose Note Count via REST API

Agar front-end bisa mengetahui berapa note yang sudah dimiliki user (untuk menyembunyikan form create):

php
// Di functions.php (dalam hook rest_api_init atau init)
register_rest_field('note', 'userNoteCount', array(
  'get_callback' => function() {
    return count_user_posts(get_current_user_id(), 'note');
  }
));

Sekarang setiap response Note dari REST API akan menyertakan field userNoteCount.

3. Front-End: Sembunyikan Form Saat Limit Tercapai

javascript
// Di createNote success callback
success: (response) => {
  // ... tambahkan note ke DOM ...

  // Cek apakah sudah mencapai limit
  if (response.userNoteCount >= 5) {
    $(".create-note").slideUp();
    $(".note-limit-message").slideDown();  // "You have reached your note limit."
  }
},
error: (response) => {
  // Tampilkan pesan error jika server menolak
  $(".note-limit-message").slideDown();
}

HTML untuk Error Message:

html
<p class="note-limit-message" style="display: none;">
  You have reached your note limit. Delete an existing note to make room for a new one.
</p>

BAGIAN 2: jQuery-Free My Notes

Mengapa Tanpa jQuery?

  • jQuery menambah ukuran bundle
  • Vanilla JavaScript modern sudah sangat capable
  • WordPress bergerak ke arah Gutenberg yang tidak menggunakan jQuery
  • Banyak developer lebih suka native API

Pengganti jQuery dengan Vanilla JS + Axios:

jQueryVanilla JS / Axios
$(selector)document.querySelector() / document.querySelectorAll()
$.ajax()axios() atau axios.delete() / axios.post()
$(e.target).parents("li")e.target.closest("li")
.data("id").getAttribute("data-id") atau .dataset.id
.val().value
.html("...").innerHTML = "..."
.addClass("x").classList.add("x")
.removeClass("x").classList.remove("x")
.slideUp()Custom CSS animation
.slideDown()Custom CSS animation
$.prependTo("#target")target.insertAdjacentHTML("afterbegin", html)
.on("click", ".child", fn)Event delegation manual
.removeAttr("readonly").removeAttribute("readonly")
.attr("readonly", "readonly").setAttribute("readonly", "readonly")

Axios Setup

Axios sudah tersedia via @wordpress/scripts atau bisa di-install:

javascript
import axios from 'axios';

Konfigurasi default header untuk nonce:

javascript
axios.defaults.headers.common['X-WP-Nonce'] = universityData.nonce;

Dengan ini, kita tidak perlu menambahkan nonce di setiap request!


Delete Note (Axios Version)

javascript
async deleteNote(e) {
  const thisNote = e.target.closest("li");

  try {
    const response = await axios.delete(
      universityData.root_url + '/wp-json/wp/v2/note/' + thisNote.getAttribute("data-id")
    );
    thisNote.style.height = thisNote.offsetHeight + 'px';
    // Trigger reflow
    requestAnimationFrame(() => {
      thisNote.classList.add("fade-out");
      // Setelah animasi selesai, hapus dari DOM
      setTimeout(() => {
        thisNote.remove();
      }, 401);
    });
  } catch(e) {
    console.log("Sorry, there was a problem.");
  }
}

Update Note (Axios Version)

javascript
async updateNote(e) {
  const thisNote = e.target.closest("li");
  const updatedData = {
    title: thisNote.querySelector(".note-title-field").value,
    content: thisNote.querySelector(".note-body-field").value
  };

  try {
    const response = await axios.post(
      universityData.root_url + '/wp-json/wp/v2/note/' + thisNote.getAttribute("data-id"),
      updatedData
    );
    this.makeNoteReadOnly(thisNote);
  } catch(e) {
    console.log("Sorry, could not update.");
  }
}

Create Note (Axios Version)

javascript
async createNote() {
  const newPostData = {
    title: document.querySelector(".new-note-title").value,
    content: document.querySelector(".new-note-body").value,
    status: 'publish'
  };

  try {
    const response = await axios.post(
      universityData.root_url + '/wp-json/wp/v2/note',
      newPostData
    );
    document.querySelector(".new-note-title").value = '';
    document.querySelector(".new-note-body").value = '';

    // Tambahkan ke DOM
    document.querySelector("#my-notes").insertAdjacentHTML("afterbegin", `
      <li data-id="${response.data.id}" class="fade-in-note">
        <input readonly class="note-title-field" value="${response.data.title.raw}">
        <span class="edit-note"><i class="fa fa-pencil" aria-hidden="true"></i> Edit</span>
        <span class="delete-note"><i class="fa fa-trash-o" aria-hidden="true"></i> Delete</span>
        <textarea readonly class="note-body-field">${response.data.content.raw}</textarea>
        <span class="update-note btn btn--blue btn--small">
          <i class="fa fa-arrow-right" aria-hidden="true"></i> Save
        </span>
      </li>
    `);

    // Cek limit
    if (response.data.userNoteCount >= 5) {
      document.querySelector(".create-note").style.display = 'none';
      document.querySelector(".note-limit-message").style.display = 'block';
    }
  } catch(e) {
    console.log("Sorry, could not create note.");
  }
}

Custom Slide Animation (CSS)

Karena tidak ada jQuery .slideUp() / .slideDown(), kita gunakan CSS transitions:

css
#my-notes li {
  overflow: hidden;
  transition: opacity 0.4s ease, height 0.4s ease, padding 0.4s ease;
}

#my-notes li.fade-out {
  opacity: 0;
  height: 0 !important;
  padding-top: 0;
  padding-bottom: 0;
}

.fade-in-note {
  animation: fadeIn 0.4s ease;
}

@keyframes fadeIn {
  from { opacity: 0; transform: translateY(-10px); }
  to { opacity: 1; transform: translateY(0); }
}

Event Delegation Tanpa jQuery

javascript
events() {
  document.querySelector("#my-notes").addEventListener("click", (e) => {
    // Delete
    if (e.target.closest(".delete-note")) {
      this.deleteNote(e);
    }
    // Edit
    if (e.target.closest(".edit-note")) {
      this.editNote(e);
    }
    // Update/Save
    if (e.target.closest(".update-note")) {
      this.updateNote(e);
    }
  });

  document.querySelector(".submit-note").addEventListener("click", this.createNote.bind(this));
}

Event delegation di vanilla JS: pasang satu listener di parent, lalu gunakan e.target.closest(".class") untuk mendeteksi elemen mana yang diklik.


Catatan CSS Tambahan

Instruktur menyebutkan bahwa ada penyesuaian CSS untuk halaman My Notes yang bisa di-copy dari course resources. CSS ini mengatur tampilan:

  • Form create note (border, padding, spacing)
  • Note list items (card-like appearance)
  • Active field highlighting (border biru saat edit mode)
  • .update-note--visible class untuk show/hide tombol Save
  • .note-limit-message styling
  • Animasi fade-out dan fade-in