Skip to content

Integration Guide

neikiri edited this page May 18, 2026 · 8 revisions

πŸ”— Integration Guide

Integrate Neiki's Editor into PHP, Vue.js, React, Laravel, and plain AJAX projects.


🐘 PHP Integration

Using the PHP Helper (Recommended)

The editor ships with php/neiki-editor.php β€” a helper class that handles asset loading, rendering, and HTML sanitization.

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 the CSS <link> and JS <script> tags and 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 server-side before saving to a database. NeikiEditor::sanitize() strips dangerous tags (<script>, <iframe>), event handler attributes (onclick, onerror), and javascript: protocol URLs.


PHP Helper API Reference

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

Using Local Assets

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

Manual PHP Integration (Without Helper)

<head>
    <script src="https://cdn.neikiri.dev/neiki-editor/neiki-editor.min.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>

βš›οΈ React Integration

Functional Component with Hooks

import { useEffect, useRef } from 'react';

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

    // Keep callback ref updated without re-initializing
    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 only

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

export default NeikiEditorComponent;

Usage:

import { useState } from 'react';
import NeikiEditorComponent from './NeikiEditorComponent';

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

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

Caution

Do not include value in the useEffect dependency array. This would destroy and recreate the editor on every keystroke. Use setContent() imperatively if you need to update content externally.


πŸ’š Vue.js Integration

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

Always call editor.destroy() in onBeforeUnmount / beforeDestroy to prevent memory leaks.


🟠 Laravel Blade

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

@once
@push('scripts')
<script src="https://cdn.neikiri.dev/neiki-editor/neiki-editor.min.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>

πŸ“‘ AJAX Auto-Save

Save content automatically as the user types:

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

const editor = new NeikiEditor('#editor', {
    onChange: debounce(function(content) {
        fetch('/api/autosave', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]')?.content
            },
            body: JSON.stringify({ content, id: articleId })
        })
        .then(res => res.json())
        .then(data => {
            document.getElementById('save-status').textContent = 'Saved ' + new Date().toLocaleTimeString();
        })
        .catch(() => {
            document.getElementById('save-status').textContent = 'Save failed';
        });
    }, 2000)
});

πŸ“‹ Integration Checklist

Before going live, verify:

  • Assets loaded once β€” CSS before JS, no duplicates
  • Textarea has a unique ID β€” required for initialization
  • Editor initialized after DOM ready β€” <script> after textarea or use DOMContentLoaded
  • Server-side sanitization β€” never trust raw HTML from clients
  • destroy() on unmount β€” required in SPA components (Vue, React)
  • CSRF token included in AJAX save requests
  • imageUploadHandler configured for production (avoids base64 bloat)
  • autosaveKey set if multiple editors on same URL edit different records

πŸ”— Related Pages

πŸš€ Getting Started Install and first steps
βš™οΈ Configuration All init options
πŸ”’ Security Sanitization, XSS protection details
πŸ“‹ API Reference getContent, setContent, destroy and all methods

Clone this wiki locally