Makina Blog

Le blog Makina-corpus

Créer facilement un plugin CKEditor 4


Une API bien fournie et documentée avec des exemples concrets vous permettront de parvenir à la création d'un plugin CKEditor en quelques minutes !

Novice en javascript et de manière générale en éditeur WYSIWYG ? Aucune inquiétude, pas besoin d'être un expert, écrire un plugin CKEditor 4 est vraiment simple et intuitif…

Partons d'un besoin concret : on doit ajouter un bouton dans l'éditeur qui permet de saisir du texte dans une autre langue que la langue du document en cours. Pour cela, une boîte de dialogue s'ouvre et propose à l'utilisateur de sélectionner une langue et de saisir du texte. Ce besoin a été résolu dans les dernières version du cœur de CKEditor, mais c'est pour l'exemple !

La saisie de ce texte sera insérée dans l'éditeur et encapsulée par une balise "span" avec l'attribut "lang" contenant le code ISO de la langue sélectionnée.

Si besoin, quand le pointeur est sur la balise "span" ou un de ses enfants, un clic droit ouvrira un menu contextuel. Dans ce menu, il y aura un lien qui au clic ouvrira la même boîte de dialogue avec les champs pré-remplies. La soumission du formulaire modifiera le contenu dans l'éditeur.

Le tout sera étonnamment développé en peu de temps et de connaissances.

Dans la documentation de CKEditor, vous devrez lire ce premier didacticiel mais aussi ce second ainsi que la gestion des filtres.

Inutile de s'attarder sur ces pages puisque tout y est très bien expliqué. Après avoir lu ces trois articles, vous avez déjà des bases suffisantes pour créer ce plugin. Et si vous êtes pressé, une simple copie et ré-adaptation du code complet fourni est pratiquement suffisant (il n'est pas totalement identique à celui fourni étape par étape) et surtout, vous passerez à côté de certains concepts importants. En effet, la simple lecture du code est vraiment intuitive même pour un novice…

Pour résumer, l'API de CKEditor vous permet de couvrir des besoins basiques avec seulement quelques déclarations :

  • vous n'avez pas à gérer les dialogues ni même les onglets dans les dialogues ou les formulaires : une simple déclaration structurelle suffit (encapsulées dans des méthodes).
  • récupération des données à partir d'un pointeur, inspection et manipulation du DOM, insertion à partir du pointeur : des méthodes d'API existent pour vous éviter ce travail, jQuery peut donc s'avérer inutile.
  • certaines callback basiques existent déjà (champs requis, etc)
  • gestion de menu contextuel, déclencheurs d'actions notamment avec les boîtes de dialogues associées aux liens du menus : là encore, une simple déclaration est suffisante !
  • gestion des filtrages, exception de surcharge de configuration bloquant votre plugin en deux lignes.

Bref, vous l'aurez compris, inutile d'être un expert javascript pour écrire un plugin CKEditor basique.

Pour reprendre le besoin du plugin cité ci-dessus, voici les sources (le plugin s'intitule "ckeditorLang"). A noter que 90% du code est issu des exemples en liens ci-dessus ; seules quelques adaptations ont été apportées mais le métier reste globalement le même. Par ailleurs, il est adapté pour une utilisation dans Drupal (utilisation de l'objet globale "Drupal". A remplacer donc) :

plugin.js :

(function() {

  // Déclaration du plugin
  CKEDITOR.plugins.add('ckeditorLang',
  {
    // Icône présent dans le répertoire "icons" à la racine du plugin
    icons: 'ckeditorLang',
    init : function(editor)
    {

      // Déclaration d'une boîte de dialogue, avec en prime:
      // - la déclaration d'ajout d'un contenu HTML inséré ainsi que les attributs rattachés (advanced filters)
     //  - la désactivation du plugin (bouton non visible) si une surcharge de la configuration exclue les balises span avec un attribut lang
      editor.addCommand('ckeditorLangDialog', new CKEDITOR.dialogCommand('ckeditorLangDialog', {
        allowedContent: 'span[lang]',
        requiredContent: 'span[lang]'
      }));

      // Ajout d'un bouton dans une zone prédéfinie par défaut avec l'action associée (boîte de dialogue)
      editor.ui.addButton('ckeditorLang', {
        label : Drupal.t( 'Insert text in a specific language' ),
        command : 'ckeditorLangDialog',
        toolbar: 'insert'
      });

      // Déclaration du fichier contenant la boîte de dialogue
      CKEDITOR.dialog.add('ckeditorLangDialog', this.path + 'dialogs/ckeditorLang.js');

      if (editor.contextMenu) {

        // Ajout d'un group de menu contextuel, avec un lien associé à une commande (dialogue)
        editor.addMenuGroup('ckeditorLangGroup');
        editor.addMenuItem('ckeditorLangItem', {
          label: Drupal.t('Edit translation'),
          icon: this.path + 'icons/ckeditorLang.png',
          command: 'ckeditorLangDialog',
          group: 'ckeditorLangGroup'
        });

        // Ajout d'un context d'affichage du lien du groupe de menu
        // si l'élément pointé est une balise span ou un de ses parents.
        // A noter qu'il n'est pas possible d'avoir une sélection plus spécifique ("lang" attribut).
        // Néanmoins, il est possible d'introduire des conditions ainsi que la position
        // des éléments dans le DOM.
        // Voir : http://docs.cksource.com/ckeditor_api/symbols/CKEDITOR.dom.node.html#getAscendant
        editor.contextMenu.addListener(function(element) {
          if (element.getAscendant('span', true)) {
            return {
              ckeditorLangItem: CKEDITOR.TRISTATE_OFF
            };
          }
        });
      }
    },
  });
} )(); 

ckeditorLang.js (dialog) :

 (function() {

  // Déclaration de la boîte de dialogue
  CKEDITOR.dialog.add('ckeditorLangDialog', function (editor) {
    return {
      title: Drupal.t('Specific language text'),
      minWidth: 400,
      minHeight: 200,

      // Contenu de la boîte de dialogue avec une tab, contenant des éléments de formulaire.
      contents: [
        {
          id: 'tab',
          label: Drupal.t('Specific language text'),
          elements: [
            // Liste de sélection, http://docs.ckeditor.com/#!/api/CKEDITOR.dialog.definition.select
            {
              type: 'select',
              id: 'language',
              label: Drupal.t('Language'),
              items: Drupal.settings.ckeditor_lang.language_options,
              // Appellé à l'ouverture de la boîte de dialogue via la méthode setupContent() dans la méthode onShow()
              setup: function(element) {
                // Mise à jour de la valeur de l'élément du formulaire avec l'attribut de l'élément
                // copié par référence dans la méthode onShow().
                this.setValue(element.getAttribute("lang"));
              },
              // Appellé à la soumission du formulaire par la méthode commitContent() de la dialog dans la méthode onOk().
              commit: function(element) {
                // Ajout/modification d'un attribut lang à l'élément avec la valeur du champs
                element.setAttribute("lang", this.getValue());
              }
            },
            // Textarea : http://docs.ckeditor.com/#!/api/CKEDITOR.dialog.definition.textarea
            {
              type: 'textarea',
              id: 'text',
              label: Drupal.t('Text'),
              // Callback de vérification
              validate : CKEDITOR.dialog.validate.notEmpty(Drupal.t('Text field cannot be empty')),
              setup: function(element) {
                this.setValue(element.getText());
              },
              commit: function(element) {
                element.setText(this.getValue());
              }
            }
          ]
        }
      ],
      // Callback appelée à l'ouverture de la boîte de dialogue
      onShow: function() {

        // Récupération de la sélection dans l'éditeur et de son premier élément
        var selection = editor.getSelection(),
            element = selection.getStartElement();

        // Si un élément existe, on récupère l'élément span (lui-même ou un de ses parents)
        if (element) {
          element = element.getAscendant('span', true);
        }

        // Si on a pas d'élément ou que ca n'est pas un span, alors on
        // considère que l'on est dans un cas d'insertion
        if (!element || element.getName() !== 'span') {
          // Création d'un élément span.
          element = editor.document.createElement('span');
          this.insertMode = true;
        }
        else {
          this.insertMode = false;
        }

        // Copie par référence dans l'instance pour une manipulation ultérieure.
        this.element = element;

        // Si ca n'est pas une insertion mais une modification, on appelle les méthodes
        // setup() des éléments du formulaire. Donc si c'est une insertion, les
        // valeurs par défaut seront utilisées.
        if (!this.insertMode) {
          this.setupContent(element);
        }
      },
      // Callback appelée à la soumission du formulaire (validé)
      onOk: function() {

        var dialog = this,
            span = this.element;

        // Appel des méthodes commit de chacun des éléments du formulaire.
        // L'élément crée ou précédemment sélectionné est mise a jour (son texte
        // et son attribut lang).
        dialog.commitContent(span);

        // Insertion de l'élément dans le DOM à la position du pointeur. Sinon,
        // il est déjà mis à jour par le commit (modification).
        if (dialog.insertMode) {
          editor.insertElement(span);
        }
      }
    };
  });
})(); 

C'est tout ! Votre plugin est désormais opérationnel. On verra dans un prochain article comment s'intégrer aux modules Drupal CKEditor et WYSIWYG.

Actualités en lien

Image
Encart D7 vers Drupal 11
04/04/2024

Migration d'un site Drupal 7 en Drupal 11

Trucs, astuces et "bouts" de code pour migrer votre site web de Drupal 7 à Drupal 11. Compte-rendu d'une conférence donnée au Drupalcamp Rennes 2024.

Voir l'article
Image
Formation Migration Drupal 10
03/04/2024

Du nouveau dans notre gamme de forma­tions Drupal

Maîtri­sez le CMS Drupal de bout en bout avec notre panel complet de forma­tions couvrant la migra­tion (notre petite dernière), l’ad­mi­nis­tra­tion, le déve­lop­pe­ment et l’in­té­gra­tion Drupal. Pour deve­nir expert, plon­gez dans l’uni­vers Drupal !

Voir l'article
Image
Encart article DrupalCamp 2024
06/03/2024

Makina Corpus, parte­naire du Drupal­Camp 2024

Nous sommes fiers d’an­non­cer que Makina Corpus est le spon­sor du Drupal­Camp à Rennes. Notre expert vous y propose une confé­rence « migrer de Drupal 7 à Drupal 10 ».

Voir l'article

Inscription à la newsletter

Nous vous avons convaincus