Lors du dernier BarCamp de Paris en mai, Amaury Balmer a présenté rapidement les pointeurs de nouveauté (news pointer), sorte de petit mot adhésif qui se colle à un endroit de l’écran d’administration pour indiquer une information importante ou une nouveauté.
La mise en place n’est pas aussi triviale que celle de la partie d’aide située en haut de l’écran car il faut faire appel à des objets qui s’affichent via des javascripts qu’il convient de préparer tout en comprenant bien les mécanismes en cause.
Sur la copie d’écran des réglages de la nouvelle version de l’extension xili-language, on voit ici deux « petits mots ».
Quelles sont les caractéristiques de ces petits mots adhésifs ?
– disparition si l’utilisateur a cliqué ‘fermer’ (dismiss) (immédiatement et lors des prochaines ouvertures du même écran de réglages)
– look standard : bandeau bleu, bouton (croix) de fermeture, icône drapeau,…
Quels sont les éléments disponibles dans la boite à outils WordPress ?
Pas facile à trouver, mais avec le mot clef « pointer », la recherche est plus facile.
Le javascript est wp-pointer.js ( dans wp-includes/js – wp-pointer.dev.js dans sa forme lisible)
La feuille de style spécifique est wp-includes/css/wp-pointer.css
Il existe une classe (« final ») donc non extensible dans wp-admin/includes/template.php (lignes > 1660) qui va servir d’exemple.
Un champ meta de l’utilisateur ‘dismissed_wp_pointers‘ stocke l’info en tableau s’il a déjà vu ce memo.
// Get dismissed pointers $dismissed = explode( ',', (string) get_user_meta( get_current_user_id(), 'dismissed_wp_pointers', true ) ); |
Une fonction ajax activée quand l’utilisateur ferme la boite dans wp-admin/admin-ajax.php (qui appelle wp_ajax_dismiss_wp_pointer dans wp-admin/includes/ajax-actions.php lignes > 1770)
Quelles sont les docs et articles disponibles ?
Les mots clés possibles sont ‘news pointer’.
– GeneralThreat
– Oikos wordpress pointer
Les limites des exemples trouvés:
– ne permettent qu’une boite par écran,
– pas d’infos sur la position de la flèche (triangle) du pointeur (haut, bas, gauche, droite ?)
Quelques pistes pour une implantation dans la classe admin de l’extension ?
Contexte : les extraits de code présentés sont dans une classe (ici xili-language v1.6.2) – pensez-y pour vos tests et les appels de fonction.
Dans un premier, des tests ont été réalisés pour placer la boite, la déclarer (avec les scripts et styles nécessaires) au moment de la création des écrans du tableau de bord et adapter le contenu selon l’endroit que l’on veut signaler. A la différence des exemples et comme dans WP, on a fait le choix de construire le javascript personnalisé qui provoque l’affichage en premier plan et au bon endroit (et non de le télécharger)
La préparation des éléments pour générer les boites: Pour fonctionner, script et feuille de style doivent être déclarés (register) et insérés (enqueue). Ici c’est fait au moment où est préparée l’architecture de l’administration du plugin.
296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 | /** * add admin menu and associated pages of admin UI * * @since 0.9.0 * @updated 0.9.6 - only for WP 2.7.X - do registering of new meta boxes and JS __(' -','xili-language') * @updated 2.4.1 - sub-pages and tab * */ function add_menu_settings_pages() { /* browser title and menu title - if empty no menu */ $this->thehook = add_options_page(__('xili-language plugin','xili-language'). ' - 1', __('Languages ©xili','xili-language'), 'manage_options', 'language_page', array( &$this, 'languages_settings' ) ); add_action('load-'.$this->thehook, array(&$this,'on_load_page')); $this->thehook2 = add_options_page(__('xili-language plugin','xili-language'). ' - 2', '', 'manage_options', 'language_front_set', array( &$this, 'languages_frontend_settings' ) ); add_action('load-'.$this->thehook2, array(&$this,'on_load_page_set')); $this->thehook4 = add_options_page(__('xili-language plugin','xili-language'). ' - 3', '', 'manage_options', 'language_expert', array( &$this, 'languages_expert' ) ); add_action('load-'.$this->thehook4, array(&$this,'on_load_page_expert')); $this->thehook3 = add_options_page(__('xili-language plugin','xili-language'). ' - 4', '', 'manage_options', 'language_support', array( &$this, 'languages_support' ) ); add_action('load-'.$this->thehook3, array(&$this,'on_load_page_support')); $this->insert_news_pointer ( 'xl_new_version' ); // pointer in menu for updated version add_action( 'admin_print_footer_scripts', array(&$this, 'print_the_pointers_js') ); } // called by each pointer function insert_news_pointer ( $case_news ) { wp_enqueue_style( 'wp-pointer' ); // enqueue one time only if multiple boxes wp_enqueue_script( 'wp-pointer', false, array('jquery') ); // enqueue one time only if multiple boxes ++$this->news_id; $this->news_case[$this->news_id] = $case_news; } // insert the pointers registered before - called by do_action admin_print_footer_scripts function print_the_pointers_js ( ) { if ( $this->news_id != 0 ) { for ($i = 1; $i <= $this->news_id; $i++) { $this->print_pointer_js ( $i ); } } } |
Le pointeur appelé lors de la déclaration d’une metabox ici le premier onglet de l’admin de xili-language
489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 | /** * Manage list of languages * @since 0.9.0 */ function on_load_page() { wp_enqueue_script('common'); wp_enqueue_script('wp-lists'); wp_enqueue_script('postbox'); add_meta_box('xili-language-sidebox-msg', __('Message','xili-language'), array(&$this,'on_sidebox_msg_content'), $this->thehook , 'side', 'core'); add_meta_box('xili-language-sidebox-info', __('Info','xili-language'), array(&$this,'on_sidebox_info_content'), $this->thehook , 'side', 'core'); if ( !is_multisite() ) add_meta_box('xili-language-sidebox-uninstall', __('Uninstall Options','xili-language'), array(&$this,'on_sidebox_uninstall_content'), $this->thehook , 'side', 'low'); $this->insert_news_pointer ( 'languages_settings' ); // news pointer 2.6.2 } |
Chaque appel indique un identifiant unique qui va permettre au constructeur du pointeur de choisir le bon contenu et la bonne balise pour marquer que l’utilisateur a déjà vu cette actualité (en appuyant le bouton « fermer »).
La construction et l’affichage de(s) boite(s) selon l’identifiant et si c’est possible (l’utilisateur ne les a pas vu).
378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 | /** * News pointer for tabs, boxes and menu * * @since 2.6.2 * */ function localize_admin_js( $case_news, $news_id ) { $about = __('Docs about xili-language', 'xili-language'); $pointer_edge = ''; $pointer_at = ''; $pointer_my = ''; switch ( $case_news ) { case 'xl_new_version' : $pointer_text = '<h3>' . esc_js( __( 'xili-language updated', 'xili-language') ) . '</h3>'; $pointer_text .= '<p>' . esc_js( sprintf( __( 'xili-language was updated to version %s', 'xili-language' ) , XILILANGUAGE_VER) ). '</p>'; $pointer_text .= '<p>' . esc_js( __( 'See settings submenu', 'xili-language' ).' “<a href="options-general.php?page=language_page">'. __('Languages ©xili','xili-language')."</a>”" ). '</p>'; $pointer_text .= '<p>' . esc_js( sprintf(__( 'Before to question dev.xiligroup support, do not forget to visit %s documentation', 'xili-language' ), '<a href="http://wiki.xiligroup.org" title="'.$about.'" >wiki</a>' ) ). '</p>'; $pointer_dismiss = 'xl-new-version-'.str_replace('.', '-', XILILANGUAGE_VER); // here contains VERSION to be re-displayed at each update ! $pointer_div = '#menu-settings'; $pointer_Offset = '0 0'; $pointer_edge = 'left'; // to be at left and indicate menu $pointer_my = 'left'; $pointer_at = 'right'; break; case 'languages_settings': $pointer_text = '<h3>' . esc_js( __( 'To define languages', 'xili-language') ) . '</h3>'; $pointer_text .= '<p>' . esc_js( __( 'This screen is designed to define the list of languages assigned to this website. Use the form below to add a new language with the help of preset list (popup) or by input your own ISO code.', 'xili-language' ) ). '</p>'; $pointer_text .= '<p>' . esc_js( sprintf(__( 'Before to question dev.xiligroup support, do not forget to visit %s documentation', 'xili-language' ), '<a href="http://wiki.xiligroup.org" title="'.$about.'" >wiki</a>' ) ). '</p>'; $pointer_dismiss = 'xl-settings-news'; $pointer_div = '#xili-language-lang-list'; $pointer_Offset = '120 13'; break; case 'languages_frontend_settings': $pointer_text = '<h3>' . esc_js( __( 'To define front-page', 'xili-language') ) . '</h3>'; $pointer_text .= '<p>' . esc_js( __( 'This screen contains selectors to define the behaviour of frontpage according languages and visitors browser.', 'xili-language' ) ). '</p>'; $pointer_text .= '<p>' . esc_js( sprintf(__( 'Before to question dev.xiligroup support, do not forget to visit %s documentation', 'xili-language' ), '<a href="http://wiki.xiligroup.org" title="'.$about.'" >wiki</a>' ) ). '</p>'; $pointer_dismiss = 'xl-frontend-news'; $pointer_div = '#post-body-content'; $pointer_Offset = '100 13'; break; case 'languages_theme_infos': $pointer_text = '<h3>' . esc_js( __( 'Infos about current theme', 'xili-language') ) . '</h3>'; $pointer_text .= '<p>' . esc_js( __( 'This metabox contains infos about the theme and the joined available language files (.mo).', 'xili-language' ) ). '</p>'; $pointer_text .= '<p>' . esc_js( sprintf(__( 'Before to question dev.xiligroup support, do not forget to visit %s documentation', 'xili-language' ), '<a href="http://wiki.xiligroup.org" title="'.$about.'" >wiki</a>' ) ). '</p>'; $pointer_dismiss = 'xl-frontend-theme-news'; $pointer_div = '#xili-language-sidebox-theme'; $pointer_Offset = '-330 0'; $pointer_edge = 'right'; $pointer_my = 'left'; $pointer_at = 'left'; break; case 'languages_expert': $pointer_text = '<h3>' . esc_js( __( 'For documented webmaster', 'xili-language') ) . '</h3>'; $pointer_text .= '<p>' . esc_js( __( 'This screen contains nice selectors and features to customize menus and other objects for your CMS multilingual website.', 'xili-language' ) ). '</p>'; $pointer_text .= '<p>' . esc_js( sprintf(__( 'Before to question dev.xiligroup support, do not forget to visit %s documentation', 'xili-language' ), '<a href="http://wiki.xiligroup.org" title="'.$about.'" >wiki</a>' ) ). '</p>'; $pointer_dismiss = 'xl-expert-news'; $pointer_div = '#post-body-content'; $pointer_Offset = '130 13'; break; case 'languages_expert_special': $pointer_text = '<h3>' . esc_js( __( 'For documented webmaster', 'xili-language') ) . '</h3>'; $pointer_text .= '<p>' . esc_js( __( 'This metabox contains advanced selectors and features to customize behaviours, style and other objects like widgets for your CMS multilingual website.', 'xili-language' ) ). '</p>'; $pointer_text .= '<p>' . esc_js( sprintf(__( 'Before to question dev.xiligroup support, do not forget to visit %s documentation', 'xili-language' ), '<a href="http://wiki.xiligroup.org" title="'.$about.'" >wiki</a>' ) ). '</p>'; $pointer_dismiss = 'xl-expert-special-news'; $pointer_div = '#xili-language-sidebox-special'; $pointer_Offset = '-10 0'; $pointer_edge = 'right'; $pointer_my = 'right top'; $pointer_at = 'left top'; break; case 'languages_support': $pointer_text = '<h3>' . esc_js( __( 'In direct with support', 'xili-language') ) . '</h3>'; $pointer_text .= '<p>' . esc_js( sprintf(__( 'Before to question dev.xiligroup support, do not forget to check needed website infos and to visit %s documentation', 'xili-language' ), '<a href="http://wiki.xiligroup.org" title="'.$about.'" >wiki</a>' ) ). '</p>'; $pointer_dismiss = 'xl-support-news'; $pointer_div = '#post-body-content'; $pointer_Offset = '400 13'; break; default: // nothing $pointer_text = ''; } // inspired from www.generalthreat.com // Get the list of dismissed pointers for the user $dismissed = explode( ',', (string) get_user_meta( get_current_user_id(), 'dismissed_wp_pointers', true ) ); if ( in_array( $pointer_dismiss, $dismissed ) && $pointer_dismiss == 'xl-new-version-'.str_replace('.', '-', XILILANGUAGE_VER) ) { $pointer_text = ''; // Check whether our pointer has been dismissed two times - to be sure the content is read ! } elseif ( in_array( $pointer_dismiss, $dismissed ) && in_array( $pointer_dismiss.'-1', $dismissed ) ) { $pointer_text = ''; } elseif ( in_array( $pointer_dismiss, $dismissed ) ) { $pointer_dismiss = $pointer_dismiss.'-1'; } return array( 'pointerText' => html_entity_decode( (string) $pointer_text, ENT_QUOTES, 'UTF-8'), // to be ready to include links inside... 'pointerDismiss' => $pointer_dismiss, 'pointerDiv' => $pointer_div, 'pointerEdge' => ( '' == $pointer_edge ) ? 'top' : $pointer_edge , // place of arrow 'pointerAt' => ( '' == $pointer_at ) ? 'left top' : $pointer_at , 'pointerMy' => ( '' == $pointer_my ) ? 'left top' : $pointer_my , 'pointerOffset' => $pointer_Offset, 'newsID' => $news_id ); } |
pointerAt : la position sur la page, pointerMy : la localisation de la boite elle-même (son coin gauche), pointerDiv : l’ID unique d’un élément html où va se placer le mémo.
Dans la page envoyée, les javascripts spécifiques sont insérés dans le bas de page (footer)
341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 | function print_pointer_js ( $indice ) { ; $args = $this->localize_admin_js( $this->news_case[$indice], $indice ); // keep content according indice if ( $args['pointerText'] != '' ) { // only if user don't read it before ?> <script type="text/javascript"> //<![CDATA[ jQuery(document).ready( function() { var strings<?php echo $indice; ?> = <?php echo json_encode( $args ); ?>; <?php /** Check that pointer support exists AND that text is not empty - inspired www.generalthreat.com */ ?> if(typeof(jQuery().pointer) != 'undefined' && strings<?php echo $indice; ?>.pointerText != '') { jQuery( strings<?php echo $indice; ?>.pointerDiv ).pointer({ content : strings<?php echo $indice; ?>.pointerText, position: { edge: strings<?php echo $indice; ?>.pointerEdge, at: strings<?php echo $indice; ?>.pointerAt, my: strings<?php echo $indice; ?>.pointerMy, offset: strings<?php echo $indice; ?>.pointerOffset }, close : function() { jQuery.post( ajaxurl, { pointer: strings<?php echo $indice; ?>.pointerDismiss, action: 'dismiss-wp-pointer' }); } }).pointer('open'); } }); //]]> </script> <?php } } |
Il s’agissait ici de décrire les résultats d’une démarche et son implantation afin d’ajouter de façon dynamique les pointeurs de nouveautés, petit mémo adhésif (post-it) qui attire l’attention des webmestres sur des éléments à prendre en compte.
Bonne découverte et tests…
M.