A propos des pointeurs de nouveauté : retour d’expérience, quelques trucs et astuces…

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 ».

Pointeur de nouveautés

Pointeur de nouveautés

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.

Cette entrée a été publiée dans Pour experts, xili-language, avec comme mot(s)-clef(s) , , , . Vous pouvez la mettre en favoris avec ce permalien.

2 réponses à A propos des pointeurs de nouveauté : retour d’expérience, quelques trucs et astuces…

  1. Lionel Pointet dit :

    J’aime bien cette fonctionnalité et le fait de préparer tous les pointeurs avant leur insertion est plutôt malin : pourquoi ne pas dans ce cas boucler sur le tableau des pointeurs directement en JS plutôt que de réécrire la fonction pour chaque ? Cela permettrait de gagner de la place sur les pages avec plusieurs pointeurs.

    J’ai développé un petit plugin se basant sur ces « petits mots » permettant de créer des scénarios pour guider un utilisateur à travers l’admin : http://wordpress.org/extend/plugins/wordpress-gps Disponible sur github : https://github.com/lpointet/WordPress-GPS

    Vous pouvez bien sûr commenter et/ou contribuer :).

  2. xiligroup dev dit :

    Très intéressante mise en place. Vais regarder cette approche de plus près sur la base de votre plugin « GPS » . A+

Laisser un commentaire