Issue #1809352 by nick_schuch, larowlan, jthorson, Wim Leers, Devin Carlson, effulgentsia, tim.plunkett, jessebeach | cweagans: Added Write tour.module and add it to core.
parent
395d3a6bf1
commit
9e49307da3
|
@ -0,0 +1,222 @@
|
|||
/* Artfully masterminded by ZURB */
|
||||
#joyRideTipContent { display: none; }
|
||||
|
||||
/* Default styles for the container */
|
||||
.joyride-tip-guide {
|
||||
position: absolute;
|
||||
background: #000;
|
||||
background: rgba(0,0,0,0.8);
|
||||
display: none;
|
||||
color: #fff;
|
||||
width: 300px;
|
||||
z-index: 101;
|
||||
top: 0; /* keeps the page from scrolling when calculating position */
|
||||
left: 0;
|
||||
font-family: "HelveticaNeue", "Helvetica Neue", "Helvetica", Helvetica, Arial, Lucida, sans-serif;
|
||||
font-weight: normal;
|
||||
-moz-border-radius: 4px;
|
||||
-webkit-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.joyride-content-wrapper {
|
||||
padding: 10px 10px 15px 15px;
|
||||
}
|
||||
|
||||
/* Mobile */
|
||||
@media only screen and (max-width: 767px) {
|
||||
.joyride-tip-guide {
|
||||
width: 95% !important;
|
||||
-moz-border-radius: 0;
|
||||
-webkit-border-radius: 0;
|
||||
border-radius: 0;
|
||||
left: 2.5% !important;
|
||||
}
|
||||
.joyride-tip-guide-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Add a little css triangle pip, older browser just miss out on the fanciness of it */
|
||||
.joyride-tip-guide span.joyride-nub {
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: 22px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border: solid 14px;
|
||||
border: solid 14px;
|
||||
}
|
||||
|
||||
.joyride-tip-guide span.joyride-nub.top {
|
||||
/*
|
||||
IE7/IE8 Don't support rgba so we set the fallback
|
||||
border color here. However, IE7/IE8 are also buggy
|
||||
in that the fallback color doesn't work for
|
||||
border-bottom-color so here we set the border-color
|
||||
and override the top,left,right colors below.
|
||||
*/
|
||||
border-color: #000;
|
||||
border-color: rgba(0,0,0,0.8);
|
||||
border-top-color: transparent !important;
|
||||
border-left-color: transparent !important;
|
||||
border-right-color: transparent !important;
|
||||
top: -28px;
|
||||
bottom: none;
|
||||
}
|
||||
|
||||
.joyride-tip-guide span.joyride-nub.bottom {
|
||||
/*
|
||||
IE7/IE8 Don't support rgba so we set the fallback
|
||||
border color here. However, IE7/IE8 are also buggy
|
||||
in that the fallback color doesn't work for
|
||||
border-top-color so here we set the border-color
|
||||
and override the bottom,left,right colors below.
|
||||
*/
|
||||
border-color: #000;
|
||||
border-color: rgba(0,0,0,0.8) !important;
|
||||
border-bottom-color: transparent !important;
|
||||
border-left-color: transparent !important;
|
||||
border-right-color: transparent !important;
|
||||
bottom: -28px;
|
||||
bottom: none;
|
||||
}
|
||||
|
||||
.joyride-tip-guide span.joyride-nub.right {
|
||||
border-color: #000;
|
||||
border-color: rgba(0,0,0,0.8) !important;
|
||||
border-top-color: transparent !important;
|
||||
border-right-color: transparent !important;
|
||||
border-bottom-color: transparent !important;
|
||||
top: 22px;
|
||||
bottom: none;
|
||||
left: auto;
|
||||
right: -28px;
|
||||
}
|
||||
|
||||
.joyride-tip-guide span.joyride-nub.left {
|
||||
border-color: #000;
|
||||
border-color: rgba(0,0,0,0.8) !important;
|
||||
border-top-color: transparent !important;
|
||||
border-left-color: transparent !important;
|
||||
border-bottom-color: transparent !important;
|
||||
top: 22px;
|
||||
left: -28px;
|
||||
right: auto;
|
||||
bottom: none;
|
||||
}
|
||||
|
||||
/* Typography */
|
||||
.joyride-tip-guide h1,.joyride-tip-guide h2,.joyride-tip-guide h3,.joyride-tip-guide h4,.joyride-tip-guide h5,.joyride-tip-guide h6 {
|
||||
line-height: 1.25;
|
||||
margin: 0;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
}
|
||||
.joyride-tip-guide h1 { font-size: 30px; }
|
||||
.joyride-tip-guide h2 { font-size: 26px; }
|
||||
.joyride-tip-guide h3 { font-size: 22px; }
|
||||
.joyride-tip-guide h4 { font-size: 18px; }
|
||||
.joyride-tip-guide h5 { font-size: 16px; }
|
||||
.joyride-tip-guide h6 { font-size: 14px; }
|
||||
.joyride-tip-guide p {
|
||||
margin: 0 0 18px 0;
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
}
|
||||
.joyride-tip-guide a {
|
||||
color: rgb(255,255,255);
|
||||
text-decoration: none;
|
||||
border-bottom: dotted 1px rgba(255,255,255,0.6);
|
||||
}
|
||||
.joyride-tip-guide a:hover {
|
||||
color: rgba(255,255,255,0.8);
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
/* Button Style */
|
||||
.joyride-tip-guide .joyride-next-tip {
|
||||
width: auto;
|
||||
padding: 6px 18px 4px;
|
||||
font-size: 13px;
|
||||
text-decoration: none;
|
||||
color: rgb(255,255,255);
|
||||
border: solid 1px rgb(0,60,180);
|
||||
background: rgb(0,99,255);
|
||||
background: -moz-linear-gradient(top, rgb(0,99,255) 0%, rgb(0,85,214) 100%);
|
||||
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgb(0,99,255)), color-stop(100%,rgb(0,85,214)));
|
||||
background: -webkit-linear-gradient(top, rgb(0,99,255) 0%,rgb(0,85,214) 100%);
|
||||
background: -o-linear-gradient(top, rgb(0,99,255) 0%,rgb(0,85,214) 100%);
|
||||
background: -ms-linear-gradient(top, rgb(0,99,255) 0%,rgb(0,85,214) 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#0063ff', endColorstr='#0055d6',GradientType=0 );
|
||||
background: linear-gradient(top, rgb(0,99,255) 0%,rgb(0,85,214) 100%);
|
||||
text-shadow: 0 -1px 0 rgba(0,0,0,0.5);
|
||||
-webkit-border-radius: 2px;
|
||||
-moz-border-radius: 2px;
|
||||
border-radius: 2px;
|
||||
-webkit-box-shadow: 0px 1px 0px rgba(255,255,255,0.3) inset;
|
||||
-moz-box-shadow: 0px 1px 0px rgba(255,255,255,0.3) inset;
|
||||
box-shadow: 0px 1px 0px rgba(255,255,255,0.3) inset;
|
||||
}
|
||||
|
||||
.joyride-next-tip:hover {
|
||||
color: rgb(255,255,255) !important;
|
||||
border: solid 1px rgb(0,60,180) !important;
|
||||
background: rgb(43,128,255);
|
||||
background: -moz-linear-gradient(top, rgb(43,128,255) 0%, rgb(29,102,211) 100%);
|
||||
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgb(43,128,255)), color-stop(100%,rgb(29,102,211)));
|
||||
background: -webkit-linear-gradient(top, rgb(43,128,255) 0%,rgb(29,102,211) 100%);
|
||||
background: -o-linear-gradient(top, rgb(43,128,255) 0%,rgb(29,102,211) 100%);
|
||||
background: -ms-linear-gradient(top, rgb(43,128,255) 0%,rgb(29,102,211) 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#2b80ff', endColorstr='#1d66d3',GradientType=0 );
|
||||
background: linear-gradient(top, rgb(43,128,255) 0%,rgb(29,102,211) 100%);
|
||||
}
|
||||
|
||||
.joyride-timer-indicator-wrap {
|
||||
width: 50px;
|
||||
height: 3px;
|
||||
border: solid 1px rgba(255,255,255,0.1);
|
||||
position: absolute;
|
||||
right: 17px;
|
||||
bottom: 16px;
|
||||
}
|
||||
.joyride-timer-indicator {
|
||||
display: block;
|
||||
width: 0;
|
||||
height: inherit;
|
||||
background: rgba(255,255,255,0.25);
|
||||
}
|
||||
|
||||
.joyride-close-tip {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 10px;
|
||||
color: rgba(255,255,255,0.4) !important;
|
||||
text-decoration: none;
|
||||
font-family: Verdana, sans-serif;
|
||||
font-size: 10px;
|
||||
font-weight: bold;
|
||||
border-bottom: none !important;
|
||||
}
|
||||
|
||||
.joyride-close-tip:hover {
|
||||
color: rgba(255,255,255,0.9) !important;
|
||||
}
|
||||
|
||||
.joyride-modal-bg {
|
||||
position: fixed;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background: rgb(0,0,0);
|
||||
background: transparent;
|
||||
background: rgba(0,0,0, 0.5);
|
||||
-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=50)";
|
||||
filter: alpha(opacity=50);
|
||||
opacity: 0.5;
|
||||
z-index: 100;
|
||||
display: none;
|
||||
top: 0;
|
||||
left: 0;
|
||||
cursor: pointer;
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
/**
|
||||
* @file
|
||||
* RTL styling for tour module.
|
||||
*/
|
||||
|
||||
.js .toolbar .bar .tour-toolbar-tab.tab {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.tour-progress {
|
||||
right: 0;
|
||||
left: 15px;
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
* @file
|
||||
* Styling for tour module.
|
||||
*/
|
||||
|
||||
/* Tab appearance. */
|
||||
.js .toolbar .bar .tour-toolbar-tab.tab {
|
||||
float: right; /* LTR */
|
||||
}
|
||||
.js .toolbar .bar .tour-toolbar-tab button {
|
||||
padding-bottom: 1em;
|
||||
padding-top: 1em;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
}
|
||||
.js .toolbar .bar .tour-toolbar-tab button.active {
|
||||
background-image: -webkit-linear-gradient(rgba(255, 255, 255, 0.25) 20%, transparent 200%);
|
||||
background-image: linear-gradient(rgba(255, 255, 255, 0.25) 20%, transparent 200%);
|
||||
}
|
||||
|
||||
/* Joyride tips should always be on top of everything else. */
|
||||
.joyride-tip-guide {
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
/* Override placement of the tour progress indicator. */
|
||||
.tour-progress {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
right: 15px; /* LTR */
|
||||
}
|
||||
|
||||
/* @todo Remove once http://drupal.org/node/1916690 is resolved. */
|
||||
.js .toolbar .bar .tour-toolbar-tab.tab.element-hidden {
|
||||
display: none;
|
||||
}
|
|
@ -0,0 +1,675 @@
|
|||
/*
|
||||
* jQuery Foundation Joyride Plugin 2.0.3
|
||||
* http://foundation.zurb.com
|
||||
* Copyright 2012, ZURB
|
||||
* Free to use under the MIT license.
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
*/
|
||||
|
||||
/*jslint unparam: true, browser: true, indent: 2 */
|
||||
|
||||
;(function ($, window, undefined) {
|
||||
'use strict';
|
||||
|
||||
var defaults = {
|
||||
'version' : '2.0.3',
|
||||
'tipLocation' : 'bottom', // 'top' or 'bottom' in relation to parent
|
||||
'nubPosition' : 'auto', // override on a per tooltip bases
|
||||
'scrollSpeed' : 300, // Page scrolling speed in milliseconds
|
||||
'timer' : 0, // 0 = no timer , all other numbers = timer in milliseconds
|
||||
'startTimerOnClick' : true, // true or false - true requires clicking the first button start the timer
|
||||
'startOffset' : 0, // the index of the tooltip you want to start on (index of the li)
|
||||
'nextButton' : true, // true or false to control whether a next button is used
|
||||
'tipAnimation' : 'fade', // 'pop' or 'fade' in each tip
|
||||
'pauseAfter' : [], // array of indexes where to pause the tour after
|
||||
'tipAnimationFadeSpeed': 300, // when tipAnimation = 'fade' this is speed in milliseconds for the transition
|
||||
'cookieMonster' : false, // true or false to control whether cookies are used
|
||||
'cookieName' : 'joyride', // Name the cookie you'll use
|
||||
'cookieDomain' : false, // Will this cookie be attached to a domain, ie. '.notableapp.com'
|
||||
'tipContainer' : 'body', // Where will the tip be attached
|
||||
'postRideCallback' : $.noop, // A method to call once the tour closes (canceled or complete)
|
||||
'postStepCallback' : $.noop, // A method to call after each step
|
||||
'template' : { // HTML segments for tip layout
|
||||
'link' : '<a href="#close" class="joyride-close-tip">X</a>',
|
||||
'timer' : '<div class="joyride-timer-indicator-wrap"><span class="joyride-timer-indicator"></span></div>',
|
||||
'tip' : '<div class="joyride-tip-guide"><span class="joyride-nub"></span></div>',
|
||||
'wrapper' : '<div class="joyride-content-wrapper" role="dialog"></div>',
|
||||
'button' : '<a href="#" class="joyride-next-tip"></a>'
|
||||
}
|
||||
},
|
||||
|
||||
Modernizr = Modernizr || false,
|
||||
|
||||
settings = {},
|
||||
|
||||
methods = {
|
||||
|
||||
init : function (opts) {
|
||||
return this.each(function () {
|
||||
|
||||
if ($.isEmptyObject(settings)) {
|
||||
settings = $.extend(true, defaults, opts);
|
||||
|
||||
// non configurable settings
|
||||
settings.document = window.document;
|
||||
settings.$document = $(settings.document);
|
||||
settings.$window = $(window);
|
||||
settings.$content_el = $(this);
|
||||
settings.body_offset = $(settings.tipContainer).position();
|
||||
settings.$tip_content = $('> li', settings.$content_el);
|
||||
settings.paused = false;
|
||||
settings.attempts = 0;
|
||||
|
||||
settings.tipLocationPatterns = {
|
||||
top: ['bottom'],
|
||||
bottom: [], // bottom should not need to be repositioned
|
||||
left: ['right', 'top', 'bottom'],
|
||||
right: ['left', 'top', 'bottom']
|
||||
};
|
||||
|
||||
// are we using jQuery 1.7+
|
||||
methods.jquery_check();
|
||||
|
||||
// can we create cookies?
|
||||
if (!$.isFunction($.cookie)) {
|
||||
settings.cookieMonster = false;
|
||||
}
|
||||
|
||||
// generate the tips and insert into dom.
|
||||
if (!settings.cookieMonster || !$.cookie(settings.cookieName)) {
|
||||
|
||||
settings.$tip_content.each(function (index) {
|
||||
methods.create({$li : $(this), index : index});
|
||||
});
|
||||
|
||||
// show first tip
|
||||
if (!settings.startTimerOnClick && settings.timer > 0) {
|
||||
methods.show('init');
|
||||
methods.startTimer();
|
||||
} else {
|
||||
methods.show('init');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
settings.$document.on('click.joyride', '.joyride-next-tip, .joyride-modal-bg', function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
if (settings.$li.next().length < 1) {
|
||||
methods.end();
|
||||
} else if (settings.timer > 0) {
|
||||
clearTimeout(settings.automate);
|
||||
methods.hide();
|
||||
methods.show();
|
||||
methods.startTimer();
|
||||
} else {
|
||||
methods.hide();
|
||||
methods.show();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
settings.$document.on('click.joyride', '.joyride-close-tip', function (e) {
|
||||
e.preventDefault();
|
||||
methods.end();
|
||||
});
|
||||
|
||||
settings.$window.bind('resize.joyride', function (e) {
|
||||
if (methods.is_phone()) {
|
||||
methods.pos_phone();
|
||||
} else {
|
||||
methods.pos_default();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
methods.restart();
|
||||
}
|
||||
|
||||
});
|
||||
},
|
||||
|
||||
// call this method when you want to resume the tour
|
||||
resume : function () {
|
||||
methods.set_li();
|
||||
methods.show();
|
||||
},
|
||||
|
||||
tip_template : function (opts) {
|
||||
var $blank, content, $wrapper;
|
||||
|
||||
opts.tip_class = opts.tip_class || '';
|
||||
|
||||
$blank = $(settings.template.tip).addClass(opts.tip_class);
|
||||
content = $.trim($(opts.li).html()) +
|
||||
methods.button_text(opts.button_text) +
|
||||
settings.template.link +
|
||||
methods.timer_instance(opts.index);
|
||||
|
||||
$wrapper = $(settings.template.wrapper);
|
||||
if (opts.li.attr('data-aria-labelledby')) {
|
||||
$wrapper.attr('aria-labelledby', opts.li.attr('data-aria-labelledby'))
|
||||
}
|
||||
if (opts.li.attr('data-aria-describedby')) {
|
||||
$wrapper.attr('aria-describedby', opts.li.attr('data-aria-describedby'))
|
||||
}
|
||||
$blank.append($wrapper);
|
||||
$blank.first().attr('data-index', opts.index);
|
||||
$('.joyride-content-wrapper', $blank).append(content);
|
||||
|
||||
return $blank[0];
|
||||
},
|
||||
|
||||
timer_instance : function (index) {
|
||||
var txt;
|
||||
|
||||
if ((index === 0 && settings.startTimerOnClick && settings.timer > 0) || settings.timer === 0) {
|
||||
txt = '';
|
||||
} else {
|
||||
txt = methods.outerHTML($(settings.template.timer)[0]);
|
||||
}
|
||||
return txt;
|
||||
},
|
||||
|
||||
button_text : function (txt) {
|
||||
if (settings.nextButton) {
|
||||
txt = $.trim(txt) || 'Next';
|
||||
txt = methods.outerHTML($(settings.template.button).append(txt)[0]);
|
||||
} else {
|
||||
txt = '';
|
||||
}
|
||||
return txt;
|
||||
},
|
||||
|
||||
create : function (opts) {
|
||||
// backwards compatibility with data-text attribute
|
||||
var buttonText = opts.$li.attr('data-button') || opts.$li.attr('data-text'),
|
||||
tipClass = opts.$li.attr('class'),
|
||||
$tip_content = $(methods.tip_template({
|
||||
tip_class : tipClass,
|
||||
index : opts.index,
|
||||
button_text : buttonText,
|
||||
li : opts.$li
|
||||
}));
|
||||
|
||||
$(settings.tipContainer).append($tip_content);
|
||||
},
|
||||
|
||||
show : function (init) {
|
||||
var opts = {}, ii, opts_arr = [], opts_len = 0, p,
|
||||
$timer = null;
|
||||
|
||||
// are we paused?
|
||||
if (settings.$li === undefined || ($.inArray(settings.$li.index(), settings.pauseAfter) === -1)) {
|
||||
|
||||
// don't go to the next li if the tour was paused
|
||||
if (settings.paused) {
|
||||
settings.paused = false;
|
||||
} else {
|
||||
methods.set_li(init);
|
||||
}
|
||||
|
||||
settings.attempts = 0;
|
||||
|
||||
if (settings.$li.length && settings.$target.length > 0) {
|
||||
opts_arr = (settings.$li.data('options') || ':').split(';');
|
||||
opts_len = opts_arr.length;
|
||||
|
||||
// parse options
|
||||
for (ii = opts_len - 1; ii >= 0; ii--) {
|
||||
p = opts_arr[ii].split(':');
|
||||
|
||||
if (p.length === 2) {
|
||||
opts[$.trim(p[0])] = $.trim(p[1]);
|
||||
}
|
||||
}
|
||||
|
||||
settings.tipSettings = $.extend({}, settings, opts);
|
||||
|
||||
settings.tipSettings.tipLocationPattern = settings.tipLocationPatterns[settings.tipSettings.tipLocation];
|
||||
|
||||
// scroll if not modal
|
||||
if (!/body/i.test(settings.$target.selector)) {
|
||||
methods.scroll_to();
|
||||
}
|
||||
|
||||
if (methods.is_phone()) {
|
||||
methods.pos_phone(true);
|
||||
} else {
|
||||
methods.pos_default(true);
|
||||
}
|
||||
|
||||
$timer = $('.joyride-timer-indicator', settings.$next_tip);
|
||||
|
||||
if (/pop/i.test(settings.tipAnimation)) {
|
||||
|
||||
$timer.outerWidth(0);
|
||||
|
||||
if (settings.timer > 0) {
|
||||
|
||||
settings.$next_tip.show();
|
||||
$timer.animate({
|
||||
width: $('.joyride-timer-indicator-wrap', settings.$next_tip).outerWidth()
|
||||
}, settings.timer);
|
||||
|
||||
} else {
|
||||
|
||||
settings.$next_tip.show();
|
||||
|
||||
}
|
||||
|
||||
|
||||
} else if (/fade/i.test(settings.tipAnimation)) {
|
||||
|
||||
$timer.outerWidth(0);
|
||||
|
||||
if (settings.timer > 0) {
|
||||
|
||||
settings.$next_tip.fadeIn(settings.tipAnimationFadeSpeed);
|
||||
|
||||
settings.$next_tip.show();
|
||||
$timer.animate({
|
||||
width: $('.joyride-timer-indicator-wrap', settings.$next_tip).outerWidth()
|
||||
}, settings.timer);
|
||||
|
||||
} else {
|
||||
|
||||
settings.$next_tip.fadeIn(settings.tipAnimationFadeSpeed);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
settings.$current_tip = settings.$next_tip;
|
||||
$('.joyride-next-tip', settings.$current_tip).focus();
|
||||
methods.tabbable(settings.$current_tip);
|
||||
|
||||
// skip non-existent targets
|
||||
} else if (settings.$li && settings.$target.length < 1) {
|
||||
|
||||
methods.show();
|
||||
|
||||
} else {
|
||||
|
||||
methods.end();
|
||||
|
||||
}
|
||||
} else {
|
||||
|
||||
settings.paused = true;
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
// detect phones with media queries if supported.
|
||||
is_phone : function () {
|
||||
if (Modernizr) {
|
||||
return Modernizr.mq('only screen and (max-width: 767px)');
|
||||
}
|
||||
|
||||
return (settings.$window.width() < 767) ? true : false;
|
||||
},
|
||||
|
||||
hide : function () {
|
||||
settings.postStepCallback(settings.$li.index(), settings.$current_tip);
|
||||
$('.joyride-modal-bg').hide();
|
||||
settings.$current_tip.hide();
|
||||
},
|
||||
|
||||
set_li : function (init) {
|
||||
if (init) {
|
||||
settings.$li = settings.$tip_content.eq(settings.startOffset);
|
||||
methods.set_next_tip();
|
||||
settings.$current_tip = settings.$next_tip;
|
||||
} else {
|
||||
settings.$li = settings.$li.next();
|
||||
methods.set_next_tip();
|
||||
}
|
||||
|
||||
methods.set_target();
|
||||
},
|
||||
|
||||
set_next_tip : function () {
|
||||
settings.$next_tip = $('.joyride-tip-guide[data-index=' + settings.$li.index() + ']');
|
||||
},
|
||||
|
||||
set_target : function () {
|
||||
var cl = settings.$li.attr('data-class'),
|
||||
id = settings.$li.attr('data-id'),
|
||||
$sel = function () {
|
||||
if (id) {
|
||||
return $(settings.document.getElementById(id));
|
||||
} else if (cl) {
|
||||
return $('.' + cl).first();
|
||||
} else {
|
||||
return $('body');
|
||||
}
|
||||
};
|
||||
|
||||
settings.$target = $sel();
|
||||
},
|
||||
|
||||
scroll_to : function () {
|
||||
var window_half, tipOffset;
|
||||
|
||||
window_half = settings.$window.height() / 2;
|
||||
tipOffset = Math.ceil(settings.$target.offset().top - window_half + settings.$next_tip.outerHeight());
|
||||
|
||||
$("html, body").stop().animate({
|
||||
scrollTop: tipOffset
|
||||
}, settings.scrollSpeed);
|
||||
},
|
||||
|
||||
paused : function () {
|
||||
if (($.inArray((settings.$li.index() + 1), settings.pauseAfter) === -1)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
destroy : function () {
|
||||
settings.$document.off('.joyride');
|
||||
$(window).off('.joyride');
|
||||
$('.joyride-close-tip, .joyride-next-tip, .joyride-modal-bg').off('.joyride');
|
||||
$('.joyride-tip-guide, .joyride-modal-bg').remove();
|
||||
clearTimeout(settings.automate);
|
||||
settings = {};
|
||||
},
|
||||
|
||||
restart : function () {
|
||||
methods.hide();
|
||||
settings.$li = undefined;
|
||||
methods.show('init');
|
||||
},
|
||||
|
||||
pos_default : function (init) {
|
||||
var half_fold = Math.ceil(settings.$window.height() / 2),
|
||||
tip_position = settings.$next_tip.offset(),
|
||||
$nub = $('.joyride-nub', settings.$next_tip),
|
||||
nub_height = Math.ceil($nub.outerHeight() / 2),
|
||||
toggle = init || false;
|
||||
|
||||
// tip must not be "display: none" to calculate position
|
||||
if (toggle) {
|
||||
settings.$next_tip.css('visibility', 'hidden');
|
||||
settings.$next_tip.show();
|
||||
}
|
||||
|
||||
if (!/body/i.test(settings.$target.selector)) {
|
||||
|
||||
if (methods.bottom()) {
|
||||
settings.$next_tip.css({
|
||||
top: (settings.$target.offset().top + nub_height + settings.$target.outerHeight()),
|
||||
left: settings.$target.offset().left});
|
||||
|
||||
methods.nub_position($nub, settings.tipSettings.nubPosition, 'top');
|
||||
|
||||
} else if (methods.top()) {
|
||||
|
||||
settings.$next_tip.css({
|
||||
top: (settings.$target.offset().top - settings.$next_tip.outerHeight() - nub_height),
|
||||
left: settings.$target.offset().left});
|
||||
|
||||
methods.nub_position($nub, settings.tipSettings.nubPosition, 'bottom');
|
||||
|
||||
} else if (methods.right()) {
|
||||
|
||||
settings.$next_tip.css({
|
||||
top: settings.$target.offset().top,
|
||||
left: (settings.$target.outerWidth() + settings.$target.offset().left)});
|
||||
|
||||
methods.nub_position($nub, settings.tipSettings.nubPosition, 'left');
|
||||
|
||||
} else if (methods.left()) {
|
||||
|
||||
settings.$next_tip.css({
|
||||
top: settings.$target.offset().top,
|
||||
left: (settings.$target.offset().left - settings.$next_tip.outerWidth() - nub_height)});
|
||||
|
||||
methods.nub_position($nub, settings.tipSettings.nubPosition, 'right');
|
||||
|
||||
}
|
||||
|
||||
if (!methods.visible(methods.corners(settings.$next_tip)) && settings.attempts < settings.tipSettings.tipLocationPattern.length) {
|
||||
|
||||
$nub.removeClass('bottom')
|
||||
.removeClass('top')
|
||||
.removeClass('right')
|
||||
.removeClass('left');
|
||||
|
||||
settings.tipSettings.tipLocation = settings.tipSettings.tipLocationPattern[settings.attempts];
|
||||
|
||||
settings.attempts++;
|
||||
|
||||
methods.pos_default(true);
|
||||
|
||||
}
|
||||
|
||||
} else if (settings.$li.length) {
|
||||
|
||||
methods.pos_modal($nub);
|
||||
|
||||
}
|
||||
|
||||
if (toggle) {
|
||||
settings.$next_tip.hide();
|
||||
settings.$next_tip.css('visibility', 'visible');
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
pos_phone : function (init) {
|
||||
var tip_height = settings.$next_tip.outerHeight(),
|
||||
tip_offset = settings.$next_tip.offset(),
|
||||
target_height = settings.$target.outerHeight(),
|
||||
$nub = $('.joyride-nub', settings.$next_tip),
|
||||
nub_height = Math.ceil($nub.outerHeight() / 2),
|
||||
toggle = init || false;
|
||||
|
||||
$nub.removeClass('bottom')
|
||||
.removeClass('top')
|
||||
.removeClass('right')
|
||||
.removeClass('left');
|
||||
|
||||
if (toggle) {
|
||||
settings.$next_tip.css('visibility', 'hidden');
|
||||
settings.$next_tip.show();
|
||||
}
|
||||
|
||||
if (!/body/i.test(settings.$target.selector)) {
|
||||
|
||||
if (methods.top()) {
|
||||
|
||||
settings.$next_tip.offset({top: settings.$target.offset().top - tip_height - nub_height});
|
||||
$nub.addClass('bottom');
|
||||
|
||||
} else {
|
||||
|
||||
settings.$next_tip.offset({top: settings.$target.offset().top + target_height + nub_height});
|
||||
$nub.addClass('top');
|
||||
|
||||
}
|
||||
|
||||
} else if (settings.$li.length) {
|
||||
|
||||
methods.pos_modal($nub);
|
||||
|
||||
}
|
||||
|
||||
if (toggle) {
|
||||
settings.$next_tip.hide();
|
||||
settings.$next_tip.css('visibility', 'visible');
|
||||
}
|
||||
},
|
||||
|
||||
pos_modal : function ($nub) {
|
||||
methods.center();
|
||||
$nub.hide();
|
||||
|
||||
if ($('.joyride-modal-bg').length < 1) {
|
||||
$('body').append('<div class="joyride-modal-bg">').show();
|
||||
}
|
||||
|
||||
if (/pop/i.test(settings.tipAnimation)) {
|
||||
$('.joyride-modal-bg').show();
|
||||
} else {
|
||||
$('.joyride-modal-bg').fadeIn(settings.tipAnimationFadeSpeed);
|
||||
}
|
||||
},
|
||||
|
||||
center : function () {
|
||||
var $w = settings.$window;
|
||||
|
||||
settings.$next_tip.css({
|
||||
top : ((($w.height() - settings.$next_tip.outerHeight()) / 2) + $w.scrollTop()),
|
||||
left : ((($w.width() - settings.$next_tip.outerWidth()) / 2) + $w.scrollLeft())
|
||||
});
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
bottom : function () {
|
||||
return /bottom/i.test(settings.tipSettings.tipLocation);
|
||||
},
|
||||
|
||||
top : function () {
|
||||
return /top/i.test(settings.tipSettings.tipLocation);
|
||||
},
|
||||
|
||||
right : function () {
|
||||
return /right/i.test(settings.tipSettings.tipLocation);
|
||||
},
|
||||
|
||||
left : function () {
|
||||
return /left/i.test(settings.tipSettings.tipLocation);
|
||||
},
|
||||
|
||||
corners : function (el) {
|
||||
var w = settings.$window,
|
||||
right = w.width() + w.scrollLeft(),
|
||||
bottom = w.width() + w.scrollTop();
|
||||
|
||||
return [
|
||||
el.offset().top <= w.scrollTop(),
|
||||
right <= el.offset().left + el.outerWidth(),
|
||||
bottom <= el.offset().top + el.outerHeight(),
|
||||
w.scrollLeft() >= el.offset().left
|
||||
];
|
||||
},
|
||||
|
||||
visible : function (hidden_corners) {
|
||||
var i = hidden_corners.length;
|
||||
|
||||
while (i--) {
|
||||
if (hidden_corners[i]) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
nub_position : function (nub, pos, def) {
|
||||
if (pos === 'auto') {
|
||||
nub.addClass(def);
|
||||
} else {
|
||||
nub.addClass(pos);
|
||||
}
|
||||
},
|
||||
|
||||
startTimer : function () {
|
||||
if (settings.$li.length) {
|
||||
settings.automate = setTimeout(function () {
|
||||
methods.hide();
|
||||
methods.show();
|
||||
methods.startTimer();
|
||||
}, settings.timer);
|
||||
} else {
|
||||
clearTimeout(settings.automate);
|
||||
}
|
||||
},
|
||||
|
||||
end : function () {
|
||||
if (settings.cookieMonster) {
|
||||
$.cookie(settings.cookieName, 'ridden', { expires: 365, domain: settings.cookieDomain });
|
||||
}
|
||||
|
||||
if (settings.timer > 0) {
|
||||
clearTimeout(settings.automate);
|
||||
}
|
||||
|
||||
$('.joyride-modal-bg').hide();
|
||||
settings.$current_tip.hide();
|
||||
settings.postStepCallback(settings.$li.index(), settings.$current_tip);
|
||||
settings.postRideCallback(settings.$li.index(), settings.$current_tip);
|
||||
},
|
||||
|
||||
jquery_check : function () {
|
||||
// define on() and off() for older jQuery
|
||||
if (!$.isFunction($.fn.on)) {
|
||||
|
||||
$.fn.on = function (types, sel, fn) {
|
||||
|
||||
return this.delegate(sel, types, fn);
|
||||
|
||||
};
|
||||
|
||||
$.fn.off = function (types, sel, fn) {
|
||||
|
||||
return this.undelegate(sel, types, fn);
|
||||
|
||||
};
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
outerHTML : function (el) {
|
||||
// support FireFox < 11
|
||||
return el.outerHTML || new XMLSerializer().serializeToString(el);
|
||||
},
|
||||
|
||||
version : function () {
|
||||
return settings.version;
|
||||
},
|
||||
|
||||
tabbable : function (el) {
|
||||
$(el).on('keydown', function( event ) {
|
||||
if (!event.isDefaultPrevented() && event.keyCode &&
|
||||
// Escape key.
|
||||
event.keyCode === 27 ) {
|
||||
event.preventDefault();
|
||||
methods.end();
|
||||
return;
|
||||
}
|
||||
|
||||
// Prevent tabbing out of tour items.
|
||||
if ( event.keyCode !== 9 ) {
|
||||
return;
|
||||
}
|
||||
var tabbables = $(el).find(":tabbable"),
|
||||
first = tabbables.filter(":first"),
|
||||
last = tabbables.filter(":last");
|
||||
if ( event.target === last[0] && !event.shiftKey ) {
|
||||
first.focus( 1 );
|
||||
event.preventDefault();
|
||||
} else if ( event.target === first[0] && event.shiftKey ) {
|
||||
last.focus( 1 );
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
$.fn.joyride = function (method) {
|
||||
if (methods[method]) {
|
||||
return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
|
||||
} else if (typeof method === 'object' || !method) {
|
||||
return methods.init.apply(this, arguments);
|
||||
} else {
|
||||
$.error('Method ' + method + ' does not exist on jQuery.joyride');
|
||||
}
|
||||
};
|
||||
|
||||
}(jQuery, this));
|
|
@ -0,0 +1,202 @@
|
|||
/**
|
||||
* @file
|
||||
* Attaches behaviors for the Tour module's toolbar tab.
|
||||
*/
|
||||
|
||||
(function ($, Backbone, Drupal, document) {
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Attaches the tour's toolbar tab behavior.
|
||||
*/
|
||||
Drupal.behaviors.tour = {
|
||||
attach: function (context) {
|
||||
var model = new Drupal.tour.models.StateModel();
|
||||
var view = new Drupal.tour.views.ToggleTourView({
|
||||
el: $(context).find('#toolbar-tab-tour'),
|
||||
model: model
|
||||
});
|
||||
|
||||
// Update the model based on Overlay events.
|
||||
$(document)
|
||||
// Overlay is opening: cancel tour if active and mark overlay as open.
|
||||
.on('drupalOverlayOpen.tour', function () {
|
||||
model.set({ isActive: false, overlayIsOpen: true });
|
||||
})
|
||||
// Overlay is loading a new URL: clear tour & cancel if active.
|
||||
.on('drupalOverlayBeforeLoad.tour', function () {
|
||||
model.set({ isActive: false, overlayTour: [] });
|
||||
})
|
||||
// Overlay is closing: clear tour & cancel if active, mark overlay closed.
|
||||
.on('drupalOverlayClose.tour', function () {
|
||||
model.set({ isActive: false, overlayIsOpen: false, overlayTour: [] });
|
||||
})
|
||||
// Overlay has loaded DOM: check whether a tour is available.
|
||||
.on('drupalOverlayReady.tour', function () {
|
||||
// We must select the tour in the Overlay's window using the Overlay's
|
||||
// jQuery, because the joyride plugin only works for the window in which
|
||||
// it was loaded.
|
||||
// @todo Make upstream contribution so this can be simplified, which
|
||||
// should also allow us to *not* load jquery.joyride.js in the Overlay,
|
||||
// resulting in better front-end performance.
|
||||
var overlay = Drupal.overlay.iframeWindow;
|
||||
var $overlayContext = overlay.jQuery(overlay.document);
|
||||
model.set('overlayTour', $overlayContext.find('#tour'));
|
||||
});
|
||||
|
||||
model
|
||||
// Allow other scripts to respond to tour events.
|
||||
.on('change:isActive', function (model, isActive) {
|
||||
$(document).trigger((isActive) ? 'drupalTourStarted' : 'drupalTourStopped');
|
||||
})
|
||||
// Initialization: check whether a tour is available on the current page.
|
||||
.set('tour', $(context).find('#tour'));
|
||||
}
|
||||
};
|
||||
|
||||
Drupal.tour = Drupal.tour || { models: {}, views: {}};
|
||||
|
||||
/**
|
||||
* Backbone Model for tours.
|
||||
*/
|
||||
Drupal.tour.models.StateModel = Backbone.Model.extend({
|
||||
defaults: {
|
||||
// Indicates whether the Drupal root window has a tour.
|
||||
tour: [],
|
||||
// Indicates whether the Overlay is open.
|
||||
overlayIsOpen: false,
|
||||
// Indicates whether the Overlay window has a tour.
|
||||
overlayTour: [],
|
||||
// Indicates whether the tour is currently running.
|
||||
isActive: false,
|
||||
// Indicates which tour is the active one (necessary to cleanly stop).
|
||||
activeTour: []
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Handles edit mode toggle interactions.
|
||||
*/
|
||||
Drupal.tour.views.ToggleTourView = Backbone.View.extend({
|
||||
|
||||
events: { 'click': 'onClick' },
|
||||
|
||||
/**
|
||||
* Implements Backbone Views' initialize().
|
||||
*/
|
||||
initialize: function () {
|
||||
this.model.on('change:tour change:overlayTour change:overlayIsOpen change:isActive', this.render, this);
|
||||
this.model.on('change:isActive', this.toggleTour, this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Implements Backbone Views' render().
|
||||
*/
|
||||
render: function () {
|
||||
// Render the visibility.
|
||||
this.$el.toggleClass('element-hidden', this._getTour().length === 0);
|
||||
// Render the state.
|
||||
var isActive = this.model.get('isActive');
|
||||
this.$el.find('button')
|
||||
.toggleClass('active', isActive)
|
||||
.attr('aria-pressed', isActive);
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Model change handler; starts or stops the tour.
|
||||
*/
|
||||
toggleTour: function() {
|
||||
if (this.model.get('isActive')) {
|
||||
var $tour = this._getTour();
|
||||
this._removeIrrelevantTourItems($tour, this._getDocument());
|
||||
var that = this;
|
||||
$tour.joyride({
|
||||
postRideCallback: function () { that.model.set('isActive', false); }
|
||||
});
|
||||
this.model.set({ isActive: true, activeTour: $tour });
|
||||
}
|
||||
else {
|
||||
this.model.get('activeTour').joyride('destroy');
|
||||
this.model.set({ isActive: false, activeTour: [] });
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Toolbar tab click event handler; toggles isActive.
|
||||
*/
|
||||
onClick: function (event) {
|
||||
this.model.set('isActive', !this.model.get('isActive'));
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the tour.
|
||||
*
|
||||
* @return jQuery
|
||||
* A jQuery element pointing to a <ol> containing tour items.
|
||||
*/
|
||||
_getTour: function () {
|
||||
var whichTour = (this.model.get('overlayIsOpen')) ? 'overlayTour' : 'tour';
|
||||
return this.model.get(whichTour);
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the relevant document as a jQuery element.
|
||||
*
|
||||
* @return jQuery
|
||||
* A jQuery element pointing to the document within which a tour would be
|
||||
* started given the current state. I.e. when the Overlay is open, this will
|
||||
* point to the HTML document inside the Overlay's iframe, otherwise it will
|
||||
* point to the Drupal root window.
|
||||
*/
|
||||
_getDocument: function () {
|
||||
return (this.model.get('overlayIsOpen')) ? $(Drupal.overlay.iframeWindow.document) : $(document);
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes tour items for elements that don't exist.
|
||||
*
|
||||
* @param jQuery $tour
|
||||
* A jQuery element pointing to a <ol> containing tour items.
|
||||
* @param jQuery $document
|
||||
* A jQuery element pointing to the document within which the elements
|
||||
* should be sought.
|
||||
*
|
||||
* @see _getDocument()
|
||||
*/
|
||||
_removeIrrelevantTourItems: function ($tour, $document) {
|
||||
var removals = false;
|
||||
$tour
|
||||
.find('li')
|
||||
.each(function () {
|
||||
var $this = $(this);
|
||||
var itemId = $this.attr('data-id');
|
||||
var itemClass = $this.attr('data-class');
|
||||
if ($document.find('#' + itemId + ', .' + itemClass).length === 0) {
|
||||
removals = true;
|
||||
$this.remove();
|
||||
}
|
||||
});
|
||||
|
||||
// If there were removals, we'll have to do some clean-up.
|
||||
if (removals) {
|
||||
var total = $tour.find('li').length;
|
||||
$tour
|
||||
.find('li')
|
||||
// Rebuild the progress data.
|
||||
.each(function (index) {
|
||||
var progress = Drupal.t('!tour_item of !total', { '!tour_item': index + 1, '!total': total });
|
||||
$(this).find('.tour-progress').text(progress);
|
||||
})
|
||||
// Update the last item to have "End tour" as the button.
|
||||
.last()
|
||||
.attr('data-text', Drupal.t('End tour'));
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
})(jQuery, Backbone, Drupal, document);
|
|
@ -0,0 +1,136 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\tour\Plugin\Core\Entity\Tour.
|
||||
*/
|
||||
|
||||
namespace Drupal\tour\Plugin\Core\Entity;
|
||||
|
||||
use Drupal\Core\Config\Entity\ConfigEntityBase;
|
||||
use Drupal\Core\Annotation\Plugin;
|
||||
use Drupal\Core\Annotation\Translation;
|
||||
use Drupal\tour\TipsBag;
|
||||
|
||||
/**
|
||||
* Defines the configured text tour entity.
|
||||
*
|
||||
* @Plugin(
|
||||
* id = "tour",
|
||||
* label = @Translation("Tour"),
|
||||
* module = "tour",
|
||||
* controller_class = "Drupal\Core\Config\Entity\ConfigStorageController",
|
||||
* config_prefix = "tour.tour",
|
||||
* entity_keys = {
|
||||
* "id" = "id",
|
||||
* "label" = "label",
|
||||
* "uuid" = "uuid",
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class Tour extends ConfigEntityBase {
|
||||
|
||||
/**
|
||||
* The name (plugin ID) of the tour.
|
||||
*
|
||||
* @var string
|
||||
* Unique identifier for this tour.
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* The label of the tour.
|
||||
*
|
||||
* @var string
|
||||
* A human readable name for this tour.
|
||||
*/
|
||||
public $label;
|
||||
|
||||
/**
|
||||
* The paths in which this tip can be displayed.
|
||||
*
|
||||
* @var array
|
||||
* An array of paths.
|
||||
*/
|
||||
protected $paths;
|
||||
|
||||
/**
|
||||
* Holds the collection of tips that are attached to this tour.
|
||||
*
|
||||
* @var \Drupal\tour\TipsBag
|
||||
*/
|
||||
protected $tipsBag;
|
||||
|
||||
/**
|
||||
* The array of plugin config, only used for export and to populate the $tipsBag.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $tips;
|
||||
|
||||
/**
|
||||
* Overrides \Drupal\Core\Config\Entity\ConfigEntityBase::__construct();
|
||||
*/
|
||||
public function __construct(array $values, $entity_type) {
|
||||
parent::__construct($values, $entity_type);
|
||||
|
||||
$this->tipsBag = new TipsBag(drupal_container()->get('plugin.manager.tour'), $this->tips);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns label of tour.
|
||||
*
|
||||
* @return string
|
||||
* The label of the tour.
|
||||
*/
|
||||
public function getLabel() {
|
||||
return $this->label;
|
||||
}
|
||||
|
||||
/**
|
||||
* The paths that this tour will appear on.
|
||||
*
|
||||
* @return array
|
||||
* Returns array of paths for the tour.
|
||||
*/
|
||||
public function getPaths() {
|
||||
return $this->paths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns tip plugin.
|
||||
*
|
||||
* @return string
|
||||
* The identifier of the tip.
|
||||
*/
|
||||
public function getTip($id) {
|
||||
return $this->tipsBag->get($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of tips.
|
||||
*
|
||||
* @return array
|
||||
* A list of tips.
|
||||
*/
|
||||
public function getTipList() {
|
||||
return array_keys($this->tips);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides \Drupal\Core\Config\Entity\ConfigEntityBase::getExportProperties();
|
||||
*/
|
||||
public function getExportProperties() {
|
||||
$properties = parent::getExportProperties();
|
||||
$names = array(
|
||||
'id',
|
||||
'label',
|
||||
'paths',
|
||||
'tips',
|
||||
);
|
||||
foreach ($names as $name) {
|
||||
$properties[$name] = $this->get($name);
|
||||
}
|
||||
return $properties;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\tour\TipPluginText.
|
||||
*/
|
||||
|
||||
namespace Drupal\tour\Plugin\tour\tip;
|
||||
|
||||
use Drupal\Core\Annotation\Plugin;
|
||||
use Drupal\tour\TipPluginBase;
|
||||
|
||||
/**
|
||||
* Displays some text as a tip.
|
||||
*
|
||||
* @Plugin(
|
||||
* id = "text",
|
||||
* module = "tour"
|
||||
* )
|
||||
*/
|
||||
class TipPluginText extends TipPluginBase {
|
||||
|
||||
/**
|
||||
* The body text which is used for render of this Text Tip.
|
||||
*
|
||||
* @var string
|
||||
* A string of text used as the body.
|
||||
*/
|
||||
protected $body;
|
||||
|
||||
/**
|
||||
* The forced position of where the tip will be located.
|
||||
*
|
||||
* @var string
|
||||
* A string of left|right|top|bottom.
|
||||
*/
|
||||
protected $location;
|
||||
|
||||
/**
|
||||
* Returns a ID that is guaranteed uniqueness.
|
||||
*
|
||||
* @return string
|
||||
* A unique string.
|
||||
*/
|
||||
public function getAriaId() {
|
||||
static $id;
|
||||
if (!isset($id)) {
|
||||
$id = drupal_html_id($this->get('id'));
|
||||
}
|
||||
return $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns body of the text tip.
|
||||
*
|
||||
* @return string
|
||||
* The body of the text tip.
|
||||
*/
|
||||
public function getBody() {
|
||||
return $this->get('body');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns location of the text tip.
|
||||
*
|
||||
* @return string
|
||||
* The location (left|right|top|bottom) of the text tip.
|
||||
*/
|
||||
public function getLocation() {
|
||||
return $this->get('location');
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides \Drupal\tour\Plugin\tour\tour\TipPluginInterface::getAttributes();
|
||||
*/
|
||||
public function getAttributes() {
|
||||
$attributes = parent::getAttributes();
|
||||
$attributes['data-aria-describedby'] = 'tour-tip-' . $this->getAriaId() . '-contents';
|
||||
$attributes['data-aria-labelledby'] = 'tour-tip-' . $this->getAriaId() . '-label';
|
||||
if ($location = $this->get('location')) {
|
||||
$attributes['data-options'] = 'tipLocation:' . $location;
|
||||
}
|
||||
return $attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides \Drupal\tour\Plugin\tour\tour\TipPluginInterface::getOutput();
|
||||
*/
|
||||
public function getOutput() {
|
||||
return array(
|
||||
'#markup' => '<h2 class="tour-tip-label" id="tour-tip-' . $this->getAriaId() . '-label">' . check_plain($this->getLabel()) . '</h2>
|
||||
<p class="tour-tip-body" id="tour-tip-' . $this->getAriaId() . '-contents">' . filter_xss_admin($this->getBody()) . '</p>'
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\tour\Tests\TourPluginTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\tour\Tests;
|
||||
|
||||
use Drupal\simpletest\DrupalUnitTestBase;
|
||||
|
||||
/**
|
||||
* Tests tour plugin functionality.
|
||||
*/
|
||||
class TourPluginTest extends DrupalUnitTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('tour');
|
||||
|
||||
/**
|
||||
* Stores the tour plugin manager.
|
||||
*
|
||||
* @var \Drupal\tour\TourManager
|
||||
*/
|
||||
protected $pluginManager;
|
||||
|
||||
/**
|
||||
* Defines test info.
|
||||
*/
|
||||
public static function getInfo() {
|
||||
return array(
|
||||
'name' => 'Tour plugin tests',
|
||||
'description' => 'Test the functionality of tour plugins.',
|
||||
'group' => 'Tour',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the test.
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
config_install_default_config('module', 'tour');
|
||||
$this->pluginManager = $this->container->get('plugin.manager.tour');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test tour plugins.
|
||||
*/
|
||||
public function testTourPlugins() {
|
||||
$this->assertIdentical(count($this->pluginManager->getDefinitions()), 1, 'Only tour plugins for the enabled modules were returned.');
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,149 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\tour\Tests\TourTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\tour\Tests;
|
||||
|
||||
use Drupal\Core\Language\Language;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
* Tests tour functionality.
|
||||
*/
|
||||
class TourTest extends WebTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('tour', 'locale', 'language', 'tour_test');
|
||||
|
||||
public static function getInfo() {
|
||||
return array(
|
||||
'name' => 'Tour tests',
|
||||
'description' => 'Test the functionality of tour tips.',
|
||||
'group' => 'Tour',
|
||||
);
|
||||
}
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->drupalLogin($this->drupalCreateUser(array('access tour', 'administer languages')));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test tour functionality.
|
||||
*/
|
||||
public function testTourFunctionality() {
|
||||
// Navigate to tour-test-1 and verify the tour_test_1 tip is found.
|
||||
$this->drupalGet('tour-test-1');
|
||||
$elements = $this->xpath('//li[@data-id=:data_id and ./h2[contains(., :text)]]', array(
|
||||
':data_id' => 'tour-test-1',
|
||||
':text' => 'The first tip'
|
||||
));
|
||||
$this->assertEqual(count($elements), 1, 'Found English variant of tip 1.');
|
||||
|
||||
$elements = $this->xpath('//li[@data-id=:data_id and ./h2[contains(., :text)]]', array(
|
||||
':data_id' => 'tour-test-2',
|
||||
':text' => 'The quick brown fox'
|
||||
));
|
||||
$this->assertNotEqual(count($elements), 1, 'Did not find English variant of tip 2.');
|
||||
|
||||
$elements = $this->xpath('//li[@data-id=:data_id and ./h2[contains(., :text)]]', array(
|
||||
':data_id' => 'tour-test-1',
|
||||
':text' => 'La pioggia cade in spagna'
|
||||
));
|
||||
$this->assertNotEqual(count($elements), 1, 'Did not find Italian variant of tip 1.');
|
||||
|
||||
// Ensure that plugin's work.
|
||||
$this->assertRaw('img src="http://local/image.png"', 'Image plugin tip found.');
|
||||
|
||||
// Navigate to tour-test-2/subpath and verify the tour_test_2 tip is found.
|
||||
$this->drupalGet('tour-test-2/subpath');
|
||||
$elements = $this->xpath('//li[@data-id=:data_id and ./h2[contains(., :text)]]', array(
|
||||
':data_id' => 'tour-test-2',
|
||||
':text' => 'The quick brown fox'
|
||||
));
|
||||
$this->assertEqual(count($elements), 1, 'Found English variant of tip 2.');
|
||||
|
||||
$elements = $this->xpath('//li[@data-id=:data_id and ./h2[contains(., :text)]]', array(
|
||||
':data_id' => 'tour-test-1',
|
||||
':text' => 'The first tip'
|
||||
));
|
||||
$this->assertNotEqual(count($elements), 1, 'Did not find English variant of tip 1.');
|
||||
|
||||
// Enable Italian language and navigate to it/tour-test1 and verify italian
|
||||
// version of tip is found.
|
||||
language_save(new Language(array('langcode' => 'it')));
|
||||
$this->drupalGet('it/tour-test-1');
|
||||
|
||||
$elements = $this->xpath('//li[@data-id=:data_id and ./h2[contains(., :text)]]', array(
|
||||
':data_id' => 'tour-test-1',
|
||||
':text' => 'La pioggia cade in spagna'
|
||||
));
|
||||
$this->assertEqual(count($elements), 1, 'Found Italian variant of tip 1.');
|
||||
|
||||
$elements = $this->xpath('//li[@data-id=:data_id and ./h2[contains(., :text)]]', array(
|
||||
':data_id' => 'tour-test-1',
|
||||
':text' => 'The first tip'
|
||||
));
|
||||
$this->assertNotEqual(count($elements), 1, 'Did not find English variant of tip 1.');
|
||||
|
||||
language_save(new Language(array('langcode' => 'en')));
|
||||
|
||||
// Programmatically create a tour for use through the remainder of the test.
|
||||
entity_create('tour', array(
|
||||
'id' => 'tour-entity-create-test-en',
|
||||
'label' => 'Tour test english',
|
||||
'langcode' => 'en',
|
||||
'paths' => array(
|
||||
'tour-test-1',
|
||||
),
|
||||
'tips' => array(
|
||||
'tour-test-1' => array(
|
||||
'id' => 'tour-code-test-1',
|
||||
'plugin' => 'text',
|
||||
'label' => 'The rain in spain',
|
||||
'body' => 'Falls mostly on the plain.',
|
||||
'weight' => '100',
|
||||
'attributes' => array(
|
||||
'data-id' => 'tour-code-test-1',
|
||||
),
|
||||
),
|
||||
),
|
||||
))->save();
|
||||
|
||||
$this->drupalGet('tour-test-1');
|
||||
|
||||
// Load it back from the database and verify storage worked.
|
||||
$entity_save_tip = entity_load('tour', 'tour-entity-create-test-en');
|
||||
// Verify that hook_ENTITY_TYPE_load() integration worked.
|
||||
$this->assertEqual($entity_save_tip->loaded, 'Load hooks work');
|
||||
// Verify that hook_ENTITY_TYPE_presave() integration worked.
|
||||
$this->assertEqual($entity_save_tip->label(), 'Tour test english alter');
|
||||
|
||||
// Navigate to tour-test-1 and verify the new tip is found.
|
||||
$this->drupalGet('tour-test-1');
|
||||
$elements = $this->xpath('//li[@data-id=:data_id and ./h2[contains(., :text)]]', array(
|
||||
':data_id' => 'tour-code-test-1',
|
||||
':text' => 'The rain in spain'
|
||||
));
|
||||
$this->assertEqual(count($elements), 1, 'Found the required tip markup for tip 4');
|
||||
|
||||
// Verify that the weight sorting works by ensuring the lower weight item
|
||||
// (tip 4) has the close button.
|
||||
$elements = $this->xpath('//li[@data-id=:data_id and ./div[contains(., :text)]]', array(
|
||||
':data_id' => 'tour-code-test-1',
|
||||
':text' => '3 of 3'
|
||||
));
|
||||
$this->assertEqual(count($elements), 1, 'Found code tip was weighted last and had "3 of 3".');
|
||||
|
||||
// Test hook_tour_alter().
|
||||
$this->assertText('Altered by hook_tour_tips_alter');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\tour\TipPluginBase.
|
||||
*/
|
||||
|
||||
namespace Drupal\tour;
|
||||
|
||||
use Drupal\Component\Plugin\PluginBase;
|
||||
use Drupal\Core\Plugin\Discovery\CacheDecorator;
|
||||
use Drupal\tour\TipPluginInterface;
|
||||
|
||||
/**
|
||||
* Defines a base tour implementation.
|
||||
*/
|
||||
abstract class TipPluginBase extends PluginBase implements TipPluginInterface {
|
||||
|
||||
/**
|
||||
* The label which is used for render of this tip.
|
||||
*
|
||||
* @var string
|
||||
* The label of this tip.
|
||||
*/
|
||||
protected $label;
|
||||
|
||||
/**
|
||||
* Allows tips to take more priority that others.
|
||||
*
|
||||
* @var string
|
||||
* A number which pertains to ordering.
|
||||
*/
|
||||
protected $weight;
|
||||
|
||||
/**
|
||||
* The attributes that will be applied to the markup of this tip.
|
||||
*
|
||||
* @var array
|
||||
* An array of attributes.
|
||||
*/
|
||||
protected $attributes;
|
||||
|
||||
/**
|
||||
* Overrides \Drupal\Component\Plugin\PluginBase::__construct().
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, CacheDecorator $discovery) {
|
||||
parent::__construct($configuration, $plugin_id, $discovery);
|
||||
$this->definition = $this->discovery->getDefinition($plugin_id);
|
||||
$this->module = $this->definition['module'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements \Drupal\tour\Plugin\tour\tour\TourInterface::getLabel().
|
||||
*/
|
||||
public function getLabel() {
|
||||
return $this->get('label');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements \Drupal\tour\Plugin\tour\tour\TourInterface::getWeight().
|
||||
*/
|
||||
public function getWeight() {
|
||||
return $this->get('weight');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements \Drupal\tour\Plugin\tour\tour\TourInterface::getAttributes().
|
||||
*/
|
||||
public function getAttributes() {
|
||||
return $this->get('attributes');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements \Drupal\tour\Plugin\tour\tour\TourInterface::get().
|
||||
*/
|
||||
public function get($key) {
|
||||
if (!empty($this->configuration[$key])) {
|
||||
return $this->configuration[$key];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements \Drupal\tour\Plugin\tour\tour\TourInterface::set().
|
||||
*/
|
||||
public function set($key, $value) {
|
||||
$this->configuration[$key] = $value;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\tour\TipPluginInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\tour;
|
||||
|
||||
/**
|
||||
* Defines an interface for tour items.
|
||||
*/
|
||||
interface TipPluginInterface {
|
||||
|
||||
/**
|
||||
* Returns label of the tip.
|
||||
*
|
||||
* @return string
|
||||
* The label of the tip.
|
||||
*/
|
||||
public function getLabel();
|
||||
|
||||
/**
|
||||
* Returns weight of the tip.
|
||||
*
|
||||
* @return string
|
||||
* The weight of the tip.
|
||||
*/
|
||||
public function getWeight();
|
||||
|
||||
/**
|
||||
* Returns an array of attributes for the tip wrapper.
|
||||
*
|
||||
* @return array
|
||||
* An array of classes and values.
|
||||
*/
|
||||
public function getAttributes();
|
||||
|
||||
/**
|
||||
* Used for returning values by key.
|
||||
*
|
||||
* @var string
|
||||
* Key of the value.
|
||||
*
|
||||
* @return string
|
||||
* Value of the key.
|
||||
*/
|
||||
public function get($key);
|
||||
|
||||
/**
|
||||
* Used for returning values by key.
|
||||
*
|
||||
* @var string
|
||||
* Key of the value.
|
||||
*
|
||||
* @var string
|
||||
* Value of the key.
|
||||
*/
|
||||
public function set($key, $value);
|
||||
|
||||
/**
|
||||
* Returns a renderable array.
|
||||
*
|
||||
* @return array
|
||||
* A renderable array.
|
||||
*/
|
||||
public function getOutput();
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\tour\TipsBag.
|
||||
*/
|
||||
|
||||
namespace Drupal\tour;
|
||||
|
||||
use Drupal\Component\Plugin\PluginBag;
|
||||
use Drupal\Component\Plugin\PluginManagerInterface;
|
||||
use Drupal\Component\Plugin\Exception\PluginException;
|
||||
use Drupal\Component\Utility\NestedArray;
|
||||
|
||||
/**
|
||||
* A collection of tips.
|
||||
*/
|
||||
class TipsBag extends PluginBag {
|
||||
/**
|
||||
* The initial configuration for each tip in the bag.
|
||||
*
|
||||
* @var array
|
||||
* An associative array containing the initial configuration for each tip
|
||||
* in the bag, keyed by plugin instance ID.
|
||||
*/
|
||||
protected $configurations = array();
|
||||
|
||||
/**
|
||||
* The manager used to instantiate the plugins.
|
||||
*
|
||||
* @var \Drupal\Component\Plugin\PluginManagerInterface
|
||||
*/
|
||||
protected $manager;
|
||||
|
||||
/**
|
||||
* Constructs a TipsBag object.
|
||||
*
|
||||
* @param \Drupal\Component\Plugin\PluginManagerInterface $manager
|
||||
* The manager to be used for instantiating plugins.
|
||||
* @param array $configurations
|
||||
* (optional) An associative array containing the initial configuration for
|
||||
* each tour in the bag, keyed by plugin instance ID.
|
||||
*/
|
||||
public function __construct(PluginManagerInterface $manager, array $configurations = array()) {
|
||||
$this->manager = $manager;
|
||||
$this->configurations = $configurations;
|
||||
|
||||
if (!empty($configurations)) {
|
||||
$this->instanceIDs = array_combine(array_keys($configurations), array_keys($configurations));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides \Drupal\Component\Plugin\PluginBag::initializePlugin().
|
||||
*/
|
||||
protected function initializePlugin($instance_id) {
|
||||
// If the tip was initialized before, just return.
|
||||
if (isset($this->pluginInstances[$instance_id])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$type = $this->configurations[$instance_id]['plugin'];
|
||||
$definition = $this->manager->getDefinition($type);
|
||||
|
||||
if (isset($definition)) {
|
||||
$this->addInstanceID($instance_id);
|
||||
$configuration = $definition;
|
||||
|
||||
// Merge the actual configuration into the default configuration.
|
||||
if (isset($this->configurations[$instance_id])) {
|
||||
$configuration = NestedArray::mergeDeep($configuration, $this->configurations[$instance_id]);
|
||||
}
|
||||
$this->pluginInstances[$instance_id] = $this->manager->createInstance($type, $configuration, $this);
|
||||
}
|
||||
else {
|
||||
throw new PluginException(format_string("Unknown tip plugin ID '@tip'.", array('@tip' => $instance_id)));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\tour\TourBundle.
|
||||
*/
|
||||
|
||||
namespace Drupal\tour;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||
|
||||
/**
|
||||
* Tour dependency injection container.
|
||||
*/
|
||||
class TourBundle extends Bundle {
|
||||
|
||||
/**
|
||||
* Overrides \Symfony\Component\HttpKernel\Bundle\Bundle::build().
|
||||
*/
|
||||
public function build(ContainerBuilder $container) {
|
||||
// Register the plugin manager for our plugin type with the dependency
|
||||
// injection container.
|
||||
$container->register('plugin.manager.tour', 'Drupal\tour\TourManager');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\tour\TourManager.
|
||||
*/
|
||||
|
||||
namespace Drupal\tour;
|
||||
|
||||
use Drupal\Component\Plugin\PluginManagerBase;
|
||||
use Drupal\Component\Plugin\Factory\DefaultFactory;
|
||||
use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery;
|
||||
use Drupal\Core\Plugin\Discovery\CacheDecorator;
|
||||
use Drupal\Component\Plugin\Discovery\ProcessDecorator;
|
||||
|
||||
/**
|
||||
* Configurable text tour manager.
|
||||
*/
|
||||
class TourManager extends PluginManagerBase {
|
||||
|
||||
/**
|
||||
* Overrides \Drupal\Component\Plugin\PluginManagerBase::__construct().
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->discovery = new AnnotatedClassDiscovery('tour', 'tip');
|
||||
$this->discovery = new ProcessDecorator($this->discovery, array($this, 'processDefinition'));
|
||||
$this->discovery = new CacheDecorator($this->discovery, 'tour');
|
||||
$this->factory = new DefaultFactory($this->discovery);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides \Drupal\Component\Plugin\PluginManagerBase::createInstance().
|
||||
*/
|
||||
public function createInstance($plugin_id, array $configuration = array(), TipsBag $bag = NULL) {
|
||||
$plugin_class = DefaultFactory::getPluginClass($plugin_id, $this->discovery);
|
||||
return new $plugin_class($configuration, $plugin_id, $this->discovery, $bag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides \Drupal\Component\Plugin\PluginManagerBase::processDefinition().
|
||||
*/
|
||||
public function processDefinition(&$definition, $plugin_id) {
|
||||
parent::processDefinition($definition, $plugin_id);
|
||||
|
||||
// @todo Remove this check once http://drupal.org/node/1780396 is resolved.
|
||||
if (!module_exists($definition['module'])) {
|
||||
$definition = NULL;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
id: tour-test-2-en
|
||||
label: Tour test english
|
||||
langcode: en
|
||||
paths:
|
||||
- tour-test-2/*
|
||||
tips:
|
||||
tour-test-2:
|
||||
id: tour-test-2-en
|
||||
plugin: text
|
||||
label: The quick brown fox
|
||||
body: Per lo più in pianura.
|
||||
weight: "2"
|
||||
attributes:
|
||||
data-id: tour-test-2
|
|
@ -0,0 +1,22 @@
|
|||
id: tour-test-en
|
||||
label: Tour test english
|
||||
langcode: en
|
||||
paths:
|
||||
- tour-test-1
|
||||
tips:
|
||||
tour-test-1:
|
||||
id: tour-test-1-en
|
||||
plugin: text
|
||||
label: The first tip
|
||||
body: Is always the best dressed.
|
||||
weight: "1"
|
||||
attributes:
|
||||
data-id: tour-test-1
|
||||
tour-test-3:
|
||||
id: tour-test-3-en
|
||||
plugin: image
|
||||
label: The awesome image
|
||||
url: http://local/image.png
|
||||
weight: "1"
|
||||
attributes:
|
||||
data-id: tour-test-3
|
|
@ -0,0 +1,14 @@
|
|||
id: tour-test-it
|
||||
label: Tour test italian
|
||||
langcode: it
|
||||
paths:
|
||||
- tour-test-1
|
||||
tips:
|
||||
tour-test-1-it:
|
||||
id: tour-test-1-it
|
||||
plugin: text
|
||||
label: La pioggia cade in spagna
|
||||
body: Per lo più in pianura.
|
||||
weight: "1"
|
||||
attributes:
|
||||
data-id: tour-test-1
|
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\tour_test\Plugin\tour\tour\TipPluginImage.
|
||||
*/
|
||||
|
||||
namespace Drupal\tour_test\Plugin\tour\tip;
|
||||
|
||||
use Drupal\Core\Annotation\Plugin;
|
||||
use Drupal\tour\TipPluginBase;
|
||||
|
||||
/**
|
||||
* Displays an image as a tip.
|
||||
*
|
||||
* @Plugin(
|
||||
* id = "image",
|
||||
* module = "tour_test"
|
||||
* )
|
||||
*/
|
||||
class TipPluginImage extends TipPluginBase {
|
||||
|
||||
/**
|
||||
* The url which is used for the image in this Tip.
|
||||
*
|
||||
* @var string
|
||||
* A url used for the image.
|
||||
*/
|
||||
protected $url;
|
||||
|
||||
/**
|
||||
* The alt text which is used for the image in this Tip.
|
||||
*
|
||||
* @var string
|
||||
* A alt text used for the image.
|
||||
*/
|
||||
protected $alt;
|
||||
|
||||
/**
|
||||
* Overrides \Drupal\tour\Plugin\tour\tour\TipPluginInterface::getOutput().
|
||||
*/
|
||||
public function getOutput() {
|
||||
return array(
|
||||
'#markup' => '<h2 class="tour-tip-label" id="tour-tip-' . $this->get('ariaId') . '-label">' . check_plain($this->get('label')) . '</h2>
|
||||
<p class="tour-tip-image" id="tour-tip-' . $this->get('ariaId') . '-contents">' . theme('image', array('uri' => $this->get('url'), 'alt' => $this->get('alt'))) . '</p>'
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
name = Tour module tests
|
||||
description = Tests module for tour module.
|
||||
package = Core
|
||||
version = VERSION
|
||||
core = 8.x
|
||||
hidden = TRUE
|
||||
dependencies[] = tour
|
|
@ -0,0 +1,87 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Provides tests for tour module
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_menu().
|
||||
*/
|
||||
function tour_test_menu() {
|
||||
$items['tour-test-1'] = array(
|
||||
'page callback' => 'tour_test_1',
|
||||
'access callback' => TRUE,
|
||||
'title' => 'Tour test 1'
|
||||
);
|
||||
$items['tour-test-2/subpath'] = array(
|
||||
'page callback' => 'tour_test_2',
|
||||
'access callback' => TRUE,
|
||||
'title' => 'Tour test 2'
|
||||
);
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_ENTITY_TYPE_load() for tour.
|
||||
*/
|
||||
function tour_test_tour_load($entities) {
|
||||
if (isset($entities['tour-entity-create-test-en'])) {
|
||||
$entities['tour-entity-create-test-en']->loaded = 'Load hooks work';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_ENTITY_TYPE_presave() for tour.
|
||||
*/
|
||||
function tour_test_tour_presave($entity) {
|
||||
if ($entity->id() == 'tour-entity-create-test-en') {
|
||||
$entity->set('label', $entity->label() . ' alter');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Page callback: output some content for testing tours.
|
||||
*/
|
||||
function tour_test_1() {
|
||||
return array(
|
||||
'tip-1' => array(
|
||||
'#type' => 'container',
|
||||
'#attributes' => array(
|
||||
'id' => 'tour-test-1',
|
||||
),
|
||||
'#children' => t('Where does the rain in Spain fail?'),
|
||||
),
|
||||
'tip-4' => array(
|
||||
'#type' => 'container',
|
||||
'#attributes' => array(
|
||||
'id' => 'tour-test-4',
|
||||
),
|
||||
'#children' => t('Tip created later?'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Page callback: output some content for testing tours.
|
||||
*/
|
||||
function tour_test_2() {
|
||||
return array(
|
||||
'#type' => 'container',
|
||||
'#attributes' => array(
|
||||
'id' => 'tour-test-2',
|
||||
),
|
||||
'#children' => t('Pangram example'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_tour_alter().
|
||||
*/
|
||||
function tour_test_tour_tips_alter(array &$tour_tips, $path) {
|
||||
foreach ($tour_tips as $tour_tip) {
|
||||
if ($tour_tip->get('id') == 'tour-code-test-1') {
|
||||
$tour_tip->set('body', 'Altered by hook_tour_tips_alter');
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Describes API functions for tour module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Allow modules to alter tour items before render.
|
||||
*
|
||||
* @param array $tour_items
|
||||
* Array of \Drupal\tour\TipPluginInterface items.
|
||||
* @param string $path
|
||||
* The path for which the tour is valid.
|
||||
*/
|
||||
function hook_tour_tips_alter(array &$tour_tips, $path) {
|
||||
foreach ($tour_tips as $tour_tip) {
|
||||
if ($tour_tip->get('id') == 'tour-code-test-1') {
|
||||
$tour_tip->set('body', 'Altered by hook_tour_tips_alter');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Act on tour objects when loaded.
|
||||
*
|
||||
* @param array $entities
|
||||
* An array of \Drupal\tour\Plugin\Core\Entity\Tour objects, indexed by id.
|
||||
*/
|
||||
function hook_tour_load($entities) {
|
||||
if (isset($entities['tour-entity-create-test-en'])) {
|
||||
$entities['tour-entity-create-test-en']->loaded = 'Load hooks work';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Act on a tour being inserted or updated.
|
||||
*
|
||||
* This hook is invoked before the tour object is saved to configuration.
|
||||
*
|
||||
* @param \Drupal\tour\Plugin\Core\Entity\Tour $entity
|
||||
* The tour object.
|
||||
*
|
||||
* @see hook_tour_insert()
|
||||
* @see hook_tour_update()
|
||||
*/
|
||||
function hook_tour_presave($entity) {
|
||||
if ($entity->id() == 'tour-entity-create-test-en') {
|
||||
$entity->set('label', $entity->label() . ' alter');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Respond to creation of a new tour.
|
||||
*
|
||||
* @param \Drupal\tour\Plugin\Core\Entity\Tour $entity
|
||||
* The tour object being inserted.
|
||||
*/
|
||||
function hook_tour_insert($entity) {
|
||||
drupal_container()->get('plugin.manager.tour')->clearCachedDefinitions();
|
||||
cache('cache_tour')->deleteTags(array('tour_items'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Respond to updates to a tour object.
|
||||
*
|
||||
* @param \Drupal\tour\Plugin\Core\Entity\Tour $entity
|
||||
* The tour object being updated.
|
||||
*/
|
||||
function hook_tour_update($entity) {
|
||||
drupal_container()->get('plugin.manager.tour')->clearCachedDefinitions();
|
||||
cache('cache_tour')->deleteTags(array('tour_items'));
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
name = Tour
|
||||
description = Provides guided tours.
|
||||
package = Core
|
||||
version = VERSION
|
||||
core = 8.x
|
|
@ -0,0 +1,205 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Main functions of the module.
|
||||
*/
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
|
||||
/**
|
||||
* Implements hook_permission().
|
||||
*/
|
||||
function tour_permission() {
|
||||
return array(
|
||||
'access tour' => array(
|
||||
'title' => t('Access tour'),
|
||||
'description' => t('View tour tips.'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_library_info().
|
||||
*/
|
||||
function tour_library_info() {
|
||||
$path = drupal_get_path('module', 'tour');
|
||||
|
||||
$libraries['tour'] = array(
|
||||
'title' => 'Tour',
|
||||
'version' => VERSION,
|
||||
'js' => array(
|
||||
// Add the JavaScript, with a group and weight such that it will run
|
||||
// before modules/overlay/overlay-parent.js.
|
||||
$path . '/js/tour.js' => array('group' => JS_LIBRARY, 'weight' => -1),
|
||||
),
|
||||
'dependencies' => array(
|
||||
array('system', 'jquery'),
|
||||
array('system', 'drupal'),
|
||||
array('system', 'backbone'),
|
||||
array('tour', 'jquery.joyride'),
|
||||
array('tour', 'tour-styling'),
|
||||
),
|
||||
);
|
||||
|
||||
$libraries['tour-styling'] = array(
|
||||
'title' => 'Tour',
|
||||
'version' => VERSION,
|
||||
'css' => array(
|
||||
$path . '/css/tour.css' => array('media' => 'screen'),
|
||||
)
|
||||
);
|
||||
|
||||
$libraries['jquery.joyride'] = array(
|
||||
'title' => 'Joyride',
|
||||
'website' => 'https://github.com/zurb/joyride',
|
||||
'version' => '2.0.3',
|
||||
'js' => array(
|
||||
$path . '/js/jquery.joyride-2.0.3.js' => array(),
|
||||
),
|
||||
'css' => array(
|
||||
$path . '/css/joyride-2.0.3.css' => array('media' => 'screen'),
|
||||
),
|
||||
'dependencies' => array(
|
||||
array('system', 'jquery'),
|
||||
array('system', 'jquery.cookie'),
|
||||
),
|
||||
);
|
||||
|
||||
return $libraries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_toolbar().
|
||||
*/
|
||||
function tour_toolbar() {
|
||||
if (!user_access('access tour')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$tab['tour'] = array(
|
||||
'#type' => 'toolbar_item',
|
||||
'tab' => array(
|
||||
'#type' => 'html_tag',
|
||||
'#tag' => 'button',
|
||||
'#value' => t('Tour'),
|
||||
'#attributes' => array(
|
||||
'class' => array('icon', 'icon-help'),
|
||||
'role' => 'button',
|
||||
'aria-pressed' => 'false',
|
||||
),
|
||||
),
|
||||
'#wrapper_attributes' => array(
|
||||
'class' => array('tour-toolbar-tab', 'element-hidden'),
|
||||
'id' => 'toolbar-tab-tour',
|
||||
),
|
||||
'#attached' => array(
|
||||
'library' => array(
|
||||
array('tour', 'tour'),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return $tab;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_preprocess_HOOK() for page.tpl.php.
|
||||
*/
|
||||
function tour_preprocess_page(&$variables) {
|
||||
$path = current_path();
|
||||
$langcode = language(LANGUAGE_TYPE_CONTENT)->langcode;
|
||||
$tour_items = array();
|
||||
// Load all of the items and match on path.
|
||||
$tour_ids = entity_query('tour')
|
||||
->condition('langcode', $langcode)
|
||||
->execute();
|
||||
$tours = entity_load_multiple('tour', $tour_ids);
|
||||
|
||||
$path_alias = drupal_strtolower(drupal_container()->get('path.alias_manager')->getPathAlias($path));
|
||||
foreach ($tours as $tour) {
|
||||
// @todo replace this with an entity_query() that does path matching when
|
||||
// http://drupal.org/node/1918768 lands.
|
||||
$pages = implode("\n", $tour->getPaths());
|
||||
if (drupal_match_path($path_alias, $pages) || (($path != $path_alias) && drupal_match_path($path, $pages))) {
|
||||
foreach ($tour->getTipList() as $id) {
|
||||
$tour_items[] = $tour->getTip($id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Allow other modules to alter.
|
||||
drupal_container()->get('module_handler')->alter('tour_tips', $tour_items, $path);
|
||||
|
||||
if (empty($tour_items)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Sort by weight.
|
||||
uasort($tour_items, function ($a, $b) {
|
||||
if ($a->getWeight() == $b->getWeight()) {
|
||||
return 0;
|
||||
}
|
||||
return ($a->getWeight() < $b->getWeight()) ? -1 : 1;
|
||||
});
|
||||
|
||||
$index = 1;
|
||||
$count = count($tour_items);
|
||||
foreach ($tour_items as $tour_item) {
|
||||
$list_items[] = array(
|
||||
'output' => $tour_item->getOutput(),
|
||||
'counter' => array(
|
||||
'#type' => 'container',
|
||||
'#attributes' => array(
|
||||
'class' => array(
|
||||
'tour-progress',
|
||||
),
|
||||
),
|
||||
'#children' => t('!tour_item of !total', array('!tour_item' => $index, '!total' => $count)),
|
||||
),
|
||||
'#wrapper_attributes' => $tour_item->getAttributes(),
|
||||
);
|
||||
$index++;
|
||||
}
|
||||
|
||||
$variables['page']['help']['tour'] = array(
|
||||
'#theme' => 'item_list',
|
||||
'#items' => $list_items,
|
||||
'#type' => 'ol',
|
||||
'#attributes' => array(
|
||||
'id' => 'tour',
|
||||
'class' => array(
|
||||
'element-hidden',
|
||||
),
|
||||
),
|
||||
'#attached' => array(
|
||||
'library' => array(
|
||||
// We must also attach the jquery.joyride library here, because it only
|
||||
// works within the window within which it is loaded. This means that if
|
||||
// we want the Tour module to work inside the Overlay, we must ensure
|
||||
// that jquery.joyride also is loaded there. (And since the Toolbar does
|
||||
// not get loaded inside the Overlay, we cannot rely on it being loaded
|
||||
// that way.)
|
||||
// If this a non-overlay page, then Drupal's dependency checking will
|
||||
// ensure this gets loaded only once.
|
||||
array('tour', 'jquery.joyride'),
|
||||
// Similarly, we must load tour's CSS, in order to style the tour tips
|
||||
// in the desired way inside the Overlay.
|
||||
array('tour', 'tour-styling'),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_tour_tour_insert().
|
||||
*/
|
||||
function tour_tour_insert($entity) {
|
||||
drupal_container()->get('plugin.manager.tour')->clearCachedDefinitions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_tour_tour_update().
|
||||
*/
|
||||
function tour_tour_update($entity) {
|
||||
drupal_container()->get('plugin.manager.tour')->clearCachedDefinitions();
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
id: views-ui-en
|
||||
label: Views ui
|
||||
langcode: en
|
||||
paths:
|
||||
- admin/structure/views/view/*/edit
|
||||
tips:
|
||||
views-ui-active-display-en:
|
||||
id: views-ui-active-display-en
|
||||
plugin: text
|
||||
label: Active display
|
||||
body: This is the active display in the view. When there are multiple displays, one link for each display is shown and you can switch displays simply by clicking on the display link.
|
||||
weight: "2"
|
||||
attributes:
|
||||
data-class: views-display-top li.active
|
||||
views-ui-displays-en:
|
||||
id: views-ui-displays-en
|
||||
plugin: text
|
||||
label: Displays in this view
|
||||
body: A view can consist of multiple displays. A display is a way of outputting the results E.g. as a page or in a block. The available displays in your view are show here.
|
||||
weight: "1"
|
||||
attributes:
|
||||
data-id: views-display-top
|
||||
views-ui-fields-en:
|
||||
id: views-ui-fields-en
|
||||
plugin: text
|
||||
label: Fields
|
||||
body: This section shows the fields output for each result. Depending on the format selected for the view, you may not see anything listed here. If the format of your view uses fields, you can click on each field displayed to configure it.
|
||||
weight: "5"
|
||||
attributes:
|
||||
data-class: views-ui-display-tab-bucket.fields
|
||||
views-ui-filter-en:
|
||||
id: views-ui-filter-en
|
||||
plugin: text
|
||||
label: Filter your view
|
||||
body: This section displays the filters you have active in your view. A filter is used to limit the results available in the output. E.g. to only show content that was <em>published</em>, you would add a filter for <em>Published</em> and select <em>Yes</em>.
|
||||
weight: "6"
|
||||
attributes:
|
||||
data-class: views-ui-display-tab-bucket.filter-criteria
|
||||
views-ui-filter-operations-en:
|
||||
id: views-ui-filter-operations-en
|
||||
plugin: text
|
||||
label: Filter actions
|
||||
body: Use this drop-button to add and re-arrange filters
|
||||
weight: "7"
|
||||
attributes:
|
||||
data-class: views-ui-display-tab-bucket.filter-criteria .dropbutton-widget
|
||||
views-ui-format-en:
|
||||
id: views-ui-format-en
|
||||
plugin: text
|
||||
label: Output format
|
||||
body: Use this section to manage the format of the output results. You can choose different ways in which the matching results are output. E.g. Choose <em>Content</em> to output each item completely, using your configured display settings. Other options include <em>Fields</em> which allows you to output only specific fields on each matching result. Additional formats can be added by installing additional module to <em>extend</em> Drupal's base functionality.
|
||||
weight: "4"
|
||||
attributes:
|
||||
data-class: views-ui-display-tab-bucket.format
|
||||
views-ui-preview-en:
|
||||
id: views-ui-preview-en
|
||||
plugin: text
|
||||
label: Preview
|
||||
body: Use this button to show a preview of the view output
|
||||
weight: "10"
|
||||
attributes:
|
||||
data-id: preview-submit
|
||||
views-ui-sorts-en:
|
||||
id: views-ui-sorts-en
|
||||
plugin: text
|
||||
label: Sort Criteria
|
||||
body: This section shows the enabled sorting criteria for the view. Sorting criteria are used to control the order in which the results are output. Clicking on any of the active sorting criteria shown in this section enables you to configure it.
|
||||
weight: "8"
|
||||
attributes:
|
||||
data-class: views-ui-display-tab-bucket.sort-criteria
|
||||
views-ui-sorts-operations-en:
|
||||
id: views-ui-sorts-operations-en
|
||||
plugin: text
|
||||
label: Sort actions
|
||||
body: Use this drop-button to add and re-arrange the sorting criteria.
|
||||
weight: "9"
|
||||
attributes:
|
||||
data-class: views-ui-display-tab-bucket.sort-criteria .dropbutton-widget
|
||||
views-ui-view-admin-en:
|
||||
id: views-ui-view-admin-en
|
||||
plugin: text
|
||||
label: View administration
|
||||
body: Use this drop-button to perform administrative tasks on the view, including adding a description and creating a clone. Click the drop button to view the available options.
|
||||
weight: "3"
|
||||
location: left
|
||||
attributes:
|
||||
data-id: views-display-extra-actions
|
|
@ -31,3 +31,4 @@ dependencies[] = file
|
|||
dependencies[] = rdf
|
||||
dependencies[] = views
|
||||
dependencies[] = views_ui
|
||||
dependencies[] = tour
|
||||
|
|
Loading…
Reference in New Issue