Skip to content

Integration Guide

JindΕ™ich Stoklasa edited this page Apr 10, 2026 · 8 revisions

πŸ”— Integration Guide

Learn how to integrate Neiki Editor into your existing projects β€” PHP, Vue.js, React, or plain AJAX.


🐘 PHP Integration

Using the PHP Helper (Recommended)

Neiki Editor ships with a PHP helper class in php/neiki-editor.php that handles asset loading, rendering, and HTML sanitization.

Tip

The PHP helper is the easiest way to integrate the editor into any PHP project β€” CMS, blog, admin panel, or custom application.

Step 1 β€” Include the helper

<?php require_once 'path/to/php/neiki-editor.php'; ?>

Step 2 β€” Output assets in <head>

<head>
    <?= NeikiEditor::assets() ?>
</head>

This outputs both the CSS <link> and JS <script> tags. It prevents duplicate includes if called multiple times.

Step 3 β€” Render the editor

<form method="POST" action="save.php">
    <?= NeikiEditor::render('content', $article->body, [
        'minHeight' => 400,
        'placeholder' => 'Write your article...',
        'theme' => 'light'
    ]) ?>
    <button type="submit">Save</button>
</form>

Step 4 β€” Sanitize on save

<?php
// save.php
require_once 'path/to/php/neiki-editor.php';

$cleanHTML = NeikiEditor::sanitize($_POST['content']);

// Save to database
$stmt = $pdo->prepare('UPDATE articles SET body = ? WHERE id = ?');
$stmt->execute([$cleanHTML, $articleId]);

Caution

Always sanitize user-submitted HTML before saving to a database. The NeikiEditor::sanitize() method strips dangerous tags (<script>, <iframe>, etc.), event handler attributes (onclick, onerror), and javascript: protocol URLs.

PHP Helper API Reference

Method Description
NeikiEditor::assets() Output CSS & JS tags from CDN. Call once per page.
NeikiEditor::assets(true, '/path/to/dist') Use local files instead of CDN.
NeikiEditor::render($id, $content, $options) Render a <textarea> with auto-initialization script.
NeikiEditor::sanitize($html) Sanitize HTML β€” strips dangerous tags and attributes.

Using Local Assets

If you're hosting the editor files yourself instead of using CDN:

<?= NeikiEditor::assets(true, '/assets/vendor/neiki-editor/dist') ?>

Manual PHP Integration

If you prefer not to use the helper class:

<head>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/neikiri/neiki-editor@2.0.0/dist/neiki-editor.css">
    <script src="https://cdn.jsdelivr.net/gh/neikiri/neiki-editor@2.0.0/dist/neiki-editor.js"></script>
</head>

<body>
    <form method="POST" action="save.php">
        <textarea id="editor" name="content"><?= htmlspecialchars($article->body) ?></textarea>
        <button type="submit">Save</button>
    </form>

    <script>
        const editor = new NeikiEditor('#editor');
    </script>
</body>

Important

When outputting user content into a <textarea>, always use htmlspecialchars() to prevent XSS. The PHP helper handles this automatically.


πŸ’Ύ AJAX Save

Save content asynchronously without a full page refresh:

const editor = new NeikiEditor('#editor', {
    onChange: debounce(function(content) {
        fetch('/api/articles/save', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
            },
            body: JSON.stringify({ content })
        })
        .then(response => response.json())
        .then(data => {
            console.log('Saved:', data);
        })
        .catch(error => {
            console.error('Save failed:', error);
        });
    }, 2000)
});

// Simple debounce utility
function debounce(fn, delay) {
    let timer;
    return function(...args) {
        clearTimeout(timer);
        timer = setTimeout(() => fn.apply(this, args), delay);
    };
}

Tip

A 2-second debounce prevents excessive API calls while the user is typing. Adjust the delay based on your needs.


🟒 Vue.js

Vue 3 (Composition API)

<template>
    <textarea ref="editorEl"></textarea>
</template>

<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue';

const props = defineProps({
    modelValue: { type: String, default: '' }
});

const emit = defineEmits(['update:modelValue']);

const editorEl = ref(null);
let editorInstance = null;

onMounted(() => {
    editorInstance = new NeikiEditor(editorEl.value, {
        onChange: (content) => {
            emit('update:modelValue', content);
        }
    });

    if (props.modelValue) {
        editorInstance.setContent(props.modelValue);
    }
});

onBeforeUnmount(() => {
    editorInstance?.destroy();
});
</script>

Usage:

<template>
    <NeikiEditorComponent v-model="article.body" />
</template>

Vue 2 (Options API)

<template>
    <textarea ref="editor"></textarea>
</template>

<script>
export default {
    props: {
        value: { type: String, default: '' }
    },
    mounted() {
        this.editor = new NeikiEditor(this.$refs.editor, {
            onChange: (content) => {
                this.$emit('input', content);
            }
        });
        if (this.value) {
            this.editor.setContent(this.value);
        }
    },
    beforeDestroy() {
        this.editor?.destroy();
    }
}
</script>

Note

Make sure to call editor.destroy() in your component's unmount/destroy lifecycle hook to prevent memory leaks.


βš›οΈ React

Functional Component with Hooks

import { useEffect, useRef, useCallback } from 'react';

function NeikiEditorComponent({ value, onChange, options = {} }) {
    const textareaRef = useRef(null);
    const editorRef = useRef(null);
    const onChangeRef = useRef(onChange);

    // Keep callback ref updated
    useEffect(() => {
        onChangeRef.current = onChange;
    }, [onChange]);

    useEffect(() => {
        if (!textareaRef.current) return;

        editorRef.current = new NeikiEditor(textareaRef.current, {
            ...options,
            onChange: (content) => {
                onChangeRef.current?.(content);
            }
        });

        if (value) {
            editorRef.current.setContent(value);
        }

        return () => {
            editorRef.current?.destroy();
            editorRef.current = null;
        };
    }, []); // Initialize once

    return <textarea ref={textareaRef} defaultValue={value} />;
}

export default NeikiEditorComponent;

Usage:

import NeikiEditorComponent from './NeikiEditorComponent';

function ArticleForm() {
    const [content, setContent] = useState('');

    return (
        <NeikiEditorComponent
            value={content}
            onChange={setContent}
            options={{ minHeight: 400, theme: 'dark' }}
        />
    );
}

Caution

Do not pass value as a dependency to the useEffect that creates the editor. This would destroy and recreate the editor on every content change. Use setContent() to update content imperatively if needed.


🧱 Laravel Blade

{{-- resources/views/components/editor.blade.php --}}

@once
@push('styles')
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/neikiri/neiki-editor@2.0.0/dist/neiki-editor.css">
@endpush
@push('scripts')
<script src="https://cdn.jsdelivr.net/gh/neikiri/neiki-editor@2.0.0/dist/neiki-editor.js"></script>
@endpush
@endonce

<textarea id="{{ $id }}" name="{{ $name }}">{!! e($value ?? '') !!}</textarea>

@push('scripts')
<script>
    new NeikiEditor('#{{ $id }}', @json($options ?? []));
</script>
@endpush

Usage in a Blade view:

<form method="POST" action="{{ route('articles.store') }}">
    @csrf
    <x-editor id="content" name="content" :value="$article->body" :options="['minHeight' => 400]" />
    <button type="submit">Save</button>
</form>

πŸ“‹ Integration Checklist

Use this checklist to ensure a complete integration:

  • Assets included β€” CSS before JS, loaded once per page
  • Textarea has unique ID β€” required for initialization
  • Editor initialized after DOM ready β€” place <script> after the textarea or use DOMContentLoaded
  • Content sanitized server-side β€” never trust raw HTML from the client
  • Destroy on unmount β€” call editor.destroy() in SPA component cleanup
  • CSRF token included β€” for AJAX save requests (if applicable)

πŸ”— Related Pages


Clone this wiki locally