jquery.tour.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530
  1. // Licensed to Cloudera, Inc. under one
  2. // or more contributor license agreements. See the NOTICE file
  3. // distributed with this work for additional information
  4. // regarding copyright ownership. Cloudera, Inc. licenses this file
  5. // to you under the Apache License, Version 2.0 (the
  6. // "License"); you may not use this file except in compliance
  7. // with the License. You may obtain a copy of the License at
  8. //
  9. // http://www.apache.org/licenses/LICENSE-2.0
  10. //
  11. // Unless required by applicable law or agreed to in writing, software
  12. // distributed under the License is distributed on an "AS IS" BASIS,
  13. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. // See the License for the specific language governing permissions and
  15. // limitations under the License.
  16. /*
  17. * jHue tour plugin
  18. * Optionally depends on $.totalstorage for progress checking and $.jHueNotify for error notification
  19. * Can be instantiated with
  20. $.jHueTour({
  21. tours: [ <-- array, the tours available for this page
  22. {
  23. name: "xxxx", <-- unique tour name (location.pathname scope)
  24. desc: "Desc yyyy", <-- the label shown on the question mark
  25. path: "beeswax/*", <-- string for the path to show this tour on
  26. steps: [ <-- array, steps of the tour
  27. {
  28. arrowOn: "a[href='/beeswax']", <-- the element relative to the popover is positioned
  29. expose: ".navbar-fixed-top", <-- optional, the exposed object. if not present, arrowOn will be exposed
  30. title: "Welcome to Beeswax!", <-- popover title
  31. content: "This is a tour of the Beeswax app. <br/><b>HTML</b> is supported <em>too!</em>", <-- popover content, html enable
  32. placement: "bottom", <-- popover placement
  33. left: "100px", <-- popover absolute position (css string)
  34. top: -20 <-- popover relative position (it adds that amount of pixels to the popover calculated position)
  35. visitUrl: "blabla?tour=hello" <-- overrides everything, redirects to specific url
  36. },
  37. {
  38. arrowOn: ".subnav-fixed",
  39. title: "Beeswax sections",
  40. content: "There are several sections in the Beeswax app",
  41. placement: "bottom",
  42. left: "100px"
  43. }, ...
  44. ],
  45. video: "http://player.vimeo.com/xxxxx", <-- instead of the steps you can specify a video and it will be displayed in a modal
  46. blog: "http://gethue.tumblr.com/yyyyy" <-- if specified, a link to this with a "Read more about it..." label will be placed under the video in the modal. if video is empty, the link will be automagically opened
  47. }, ...
  48. ]
  49. });
  50. Calling $.jHueTour({tours: [...]}) more than once will merge the tour data, so you can keep adding tours dynamically to the same page
  51. You can interact with:
  52. - $.jHueTour("start") / $.jHueTour("show") / $.jHueTour("play") : starts the first available tour
  53. - $.jHueTour("stop") / $.jHueTour("close") / $.jHueTour("hide") / $.jHueTour("stop") : starts the first available tour
  54. - $.jHueTour("reset") : removes stored tours and history
  55. - $.jHueTour("clear") : removes current tours
  56. - $.jHueTour("http://remote/hue/tour.hue") : loads a remote tour
  57. - $.jHueTour("tourName", 1) : loads tour name and start at step 1
  58. - $.jHueTour() : returns the available tours
  59. */
  60. (function ($, window, document, undefined) {
  61. var pluginName = "jHueTour",
  62. defaults = {
  63. labels: {
  64. AVAILABLE_TOURS: "Available tours",
  65. NO_AVAILABLE_TOURS: "None for this page",
  66. MORE_INFO: "Read more about it...",
  67. TOOLTIP_TITLE: "Demo tutorials"
  68. },
  69. tours: [],
  70. showRemote: false,
  71. hideIfNoneAvailable: true
  72. };
  73. function Plugin(element, options) {
  74. this.element = element;
  75. if (typeof jHueTourGlobals !== undefined) {
  76. var extendedDefaults = $.extend({}, defaults, jHueTourGlobals);
  77. extendedDefaults.labels = $.extend({}, defaults.labels, jHueTourGlobals.labels);
  78. this.options = $.extend({}, extendedDefaults, options);
  79. this.options = $.extend({}, defaults, this.options);
  80. }
  81. else {
  82. this.options = $.extend({}, defaults, options);
  83. }
  84. this._defaults = defaults;
  85. this._name = pluginName;
  86. this.currentTour = {
  87. name: "",
  88. path: "",
  89. desc: "",
  90. remote: false,
  91. steps: [],
  92. shownStep: 0,
  93. video: "",
  94. blog: ""
  95. };
  96. this.init();
  97. }
  98. Plugin.prototype.init = function () {
  99. var _this = this;
  100. _this.initQuestionMark();
  101. var _tourMask = $("<div>").attr("id", "jHueTourMask");
  102. _tourMask.width($(document).width()).height($(document).height())
  103. _tourMask.click(function () {
  104. _this.closeCurtains();
  105. });
  106. _tourMask.appendTo($("body"));
  107. $(document).on("keyup", function (e) {
  108. var _code = (e.keyCode ? e.keyCode : e.which);
  109. if ($("#jHueTourMask").is(":visible") && _code == 27) {
  110. _this.performOperation("close");
  111. }
  112. });
  113. };
  114. Plugin.prototype.initQuestionMark = function () {
  115. var _this = this;
  116. $("#jHueTourFlag").remove();
  117. var _questionMark = $("<a>").attr("id", "jHueTourFlag").html('<i class="fa fa-flag-checkered" style=""></i>');
  118. _questionMark.tooltip({
  119. placement: "bottom",
  120. title: _this.options.labels.TOOLTIP_TITLE
  121. });
  122. if ($.totalStorage("jHueTourExtras") != null) {
  123. var _newTours = [];
  124. $.each(_this.options.tours, function (cnt, tour) {
  125. if (tour.remote == undefined || !tour.remote) {
  126. _newTours.push(tour);
  127. }
  128. });
  129. _this.options.tours = _newTours.concat($.totalStorage("jHueTourExtras"));
  130. }
  131. var _toursHtml = '<ul class="nav nav-pills nav-stacked" style="margin-bottom: 0">'
  132. var _added = 0;
  133. $.each(_this.options.tours, function (ctn, tour) {
  134. if (tour.path === undefined || RegExp(tour.path).test(location.pathname)) {
  135. var _tourDone = '';
  136. var _removeTour = '';
  137. var _extraIcon = '<i class="fa fa-flag"></i> ';
  138. if ($.totalStorage !== undefined) {
  139. var _key = location.pathname;
  140. if (tour.path !== undefined && tour.path != "") {
  141. _key = tour.path;
  142. }
  143. _key += "_" + tour.name;
  144. if ($.totalStorage("jHueTourHistory") != null && $.totalStorage("jHueTourHistory")[_key] == true) {
  145. _tourDone = '<div style="color:green;float:right;margin:4px"><i class="fa fa-check-circle"></i></div>';
  146. }
  147. }
  148. if (tour.remote) {
  149. _removeTour = '<div style="color:red;float:right;margin:4px;cursor: pointer" onclick="javascript:$.jHueTour(\'remove_' + tour.name + '\')"><i class="fa fa-times-circle"></i></div>';
  150. }
  151. var _link = '<a href="javascript:$.jHueTour(\'' + tour.name + '\', 1)" style="padding:2px">';
  152. if (typeof tour.video != "undefined" && tour.video != null && tour.video != ""){
  153. _extraIcon = '<i class="fa fa-youtube-play"></i> ';
  154. }
  155. else if (typeof tour.blog != "undefined" && tour.blog != null && tour.blog != ""){
  156. _extraIcon = '<i class="fa fa-external-link"></i> ';
  157. _link = '<a href="' + tour.blog + '" target="_blank" style="padding:2px">';
  158. }
  159. _toursHtml += '<li>' + _removeTour + _tourDone + _link + _extraIcon + tour.desc + '</a></li>';
  160. _added++;
  161. }
  162. });
  163. if (_added == 0) {
  164. if (_this.options.hideIfNoneAvailable){
  165. _questionMark.css("display", "none");
  166. }
  167. else {
  168. _toursHtml += '<li>' + _this.options.labels.NO_AVAILABLE_TOURS + '</li>';
  169. }
  170. }
  171. if (_added > 0 && typeof $.totalStorage !== "undefined" && ($.totalStorage("jHueTourHideModal") == null || $.totalStorage("jHueTourHideModal") == false) && !RegExp("/accounts/login").test(location.pathname)) {
  172. $(document).ready(function () {
  173. $("#jHueTourModal").modal();
  174. $.totalStorage("jHueTourHideModal", true);
  175. $("#jHueTourModalChk").attr("checked", "checked");
  176. $("#jHueTourModalChk").on("change", function () {
  177. $.totalStorage("jHueTourHideModal", $(this).is(":checked"));
  178. });
  179. $("#jHueTourModalClose").on("click", function () {
  180. _questionMark.click();
  181. $("#jHueTourModal").modal("hide");
  182. });
  183. });
  184. }
  185. if (_this.options.showRemote){
  186. _toursHtml += '<li>' +
  187. ' <div class="input-append" style="margin-top: 10px">' +
  188. ' <input id="jHueTourRemoteTutorial" style="width:70%" type="text" placeholder="URL">' +
  189. ' <button id="jHueTourRemoteTutorialBtn" class="btn" type="button" onclick="javascript:$.jHueTour($(\'#jHueTourRemoteTutorial\').val())">' +
  190. ' <i class="fa fa-cloud-download"></i></button>' +
  191. ' </div>' +
  192. '</li>';
  193. }
  194. _toursHtml += '</ul>';
  195. _questionMark.click(function () {
  196. var _closeBtn = $("<a>");
  197. _closeBtn.html('<i class="fa fa-times"></i>').css("cursor", "pointer").css("padding", "5px").css("padding-left", "17px").css("padding-right", "7px").css("float", "right").css("margin-top", "-4px").css("margin-right", "-6px");
  198. _closeBtn.click(function () {
  199. $(".popover").remove();
  200. });
  201. _questionMark.popover("destroy").popover({
  202. title: _this.options.labels.AVAILABLE_TOURS,
  203. content: _toursHtml,
  204. html: true,
  205. trigger: "click",
  206. placement: "bottomRight"
  207. }).popover("show");
  208. if ($(".popover").position().top <= 0) {
  209. $(".popover").css("top", "10px");
  210. }
  211. _closeBtn.prependTo($(".popover-title"));
  212. });
  213. _questionMark.appendTo($("#jHueTourFlagPlaceholder"));
  214. };
  215. Plugin.prototype.addTours = function (options) {
  216. var _this = this;
  217. var _addableTours = [];
  218. if (options.tours != null) {
  219. $.each(options.tours, function (cnt, tour) {
  220. var _add = true;
  221. if (_this.options.tours != null) {
  222. $.each(_this.options.tours, function (icnt, itour) {
  223. if (itour.name == tour.name) {
  224. _add = false;
  225. }
  226. });
  227. }
  228. if (_add) {
  229. _addableTours.push(tour);
  230. }
  231. });
  232. }
  233. _this.options.tours = _this.options.tours.concat(_addableTours);
  234. };
  235. Plugin.prototype.availableTours = function () {
  236. return this.options.tours;
  237. };
  238. Plugin.prototype.performOperation = function (operation) {
  239. var _this = this;
  240. var _op = operation.toLowerCase();
  241. if (_op.indexOf("http:") == 0) {
  242. $("#jHueTourRemoteTutorial").attr("disabled", "disabled");
  243. $("#jHueTourRemoteTutorialBtn").attr("disabled", "disabled");
  244. $.ajax({
  245. type: "GET",
  246. url: operation + "?callback=?",
  247. async: false,
  248. jsonpCallback: "jHueRemoteTour",
  249. contentType: "application/json",
  250. dataType: "jsonp",
  251. success: function (json) {
  252. if ($.totalStorage !== undefined) {
  253. if ($.totalStorage("jHueTourExtras") == null) {
  254. $.totalStorage("jHueTourExtras", []);
  255. }
  256. var _newStoredArray = [];
  257. if (json.tours != null) {
  258. _newStoredArray = json.tours;
  259. $.each($.totalStorage("jHueTourExtras"), function (cnt, tour) {
  260. var _found = false;
  261. $.each(json.tours, function (icnt, itour) {
  262. if (itour.name == tour.name) {
  263. _found = true;
  264. }
  265. });
  266. if (!_found) {
  267. _newStoredArray.push(tour);
  268. }
  269. });
  270. }
  271. $.totalStorage("jHueTourExtras", _newStoredArray);
  272. }
  273. $("#jHueTourFlag").popover("destroy");
  274. _this.initQuestionMark();
  275. $("#jHueTourFlag").click();
  276. },
  277. error: function (e) {
  278. $(document).trigger("error", e.message);
  279. $("#jHueTourRemoteTutorial").removeAttr("disabled");
  280. $("#jHueTourRemoteTutorialBtn").removeAttr("disabled");
  281. }
  282. });
  283. }
  284. if (_op.indexOf("remove_") == 0) {
  285. var _tourName = _op.substr(7);
  286. if ($.totalStorage !== undefined) {
  287. var _newStoredArray = [];
  288. $.each($.totalStorage("jHueTourExtras"), function (cnt, tour) {
  289. if (tour.name != _tourName) {
  290. _newStoredArray.push(tour);
  291. }
  292. });
  293. $.totalStorage("jHueTourExtras", _newStoredArray);
  294. $("#jHueTourFlag").popover("destroy");
  295. _this.initQuestionMark();
  296. $("#jHueTourFlag").click();
  297. }
  298. }
  299. if (_op == "start" || _op == "show" || _op == "play") {
  300. if (_this.options.tours.length > 0 && _this.currentTour.name == "") {
  301. _this.currentTour.name = _this.options.tours[0].name;
  302. _this.currentTour.path = _this.options.tours[0].path;
  303. _this.currentTour.steps = _this.options.tours[0].steps;
  304. _this.currentTour.desc = _this.options.tours[0].desc;
  305. _this.currentTour.video = _this.options.tours[0].video;
  306. _this.currentTour.blog = _this.options.tours[0].blog;
  307. }
  308. this.showStep(1);
  309. }
  310. if (_op == "reset") {
  311. if ($.totalStorage !== undefined) {
  312. $.totalStorage("jHueTourHistory", null);
  313. $.totalStorage("jHueTourExtras", null);
  314. }
  315. }
  316. if (_op == "clear") {
  317. _this.options.tours = [];
  318. }
  319. if (_op == "end" || _op == "hide" || _op == "close" || _op == "stop") {
  320. _this.closeCurtains();
  321. }
  322. };
  323. Plugin.prototype.closeCurtains = function () {
  324. $(".popover").remove();
  325. $(".jHueTourExposed").removeClass("jHueTourExposed");
  326. $("#jHueTourMask").hide();
  327. };
  328. Plugin.prototype.showTour = function (tourName, stepNo) {
  329. var _this = this;
  330. if (_this.options.tours != null) {
  331. $.each(_this.options.tours, function (cnt, tour) {
  332. if (tour.name == tourName && (tour.path === undefined || RegExp(tour.path).test(location.pathname))) {
  333. _this.currentTour.name = tour.name;
  334. _this.currentTour.path = tour.path;
  335. _this.currentTour.steps = tour.steps;
  336. _this.currentTour.desc = tour.desc;
  337. _this.currentTour.video = tour.video;
  338. _this.currentTour.blog = tour.blog;
  339. if (stepNo === undefined) {
  340. _this.showStep(1);
  341. }
  342. else {
  343. _this.showStep(stepNo);
  344. }
  345. return;
  346. }
  347. });
  348. }
  349. };
  350. Plugin.prototype.showStep = function (stepNo) {
  351. var _this = this;
  352. if (typeof _this.currentTour.video != "undefined" && _this.currentTour.video != null && _this.currentTour.video != "") {
  353. if ($("#jHueTourVideoPlayer").length == 0) {
  354. var _playerHTML = '<div class="modal-header">' +
  355. '<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>' +
  356. '<h3>' + _this.currentTour.desc + '</h3>' +
  357. '</div>' +
  358. '<div class="modal-body">' +
  359. '<iframe id="jHueTourVideoFrame" src="' + _this.currentTour.video + '?autoplay=1" width="700" height="350" frameborder="0" webkitallowfullscreen="" mozallowfullscreen="" allowfullscreen="" style="height:360px;width:640px"></iframe><div class="moreInfo">' +
  360. (typeof _this.currentTour.blog != "undefined" && _this.currentTour.blog != "" ? '<br/><a href="' + _this.currentTour.blog + '" target="_blank"><i class="fa fa-external-link"></i> ' + _this.options.labels.MORE_INFO + '</a>' : '') +
  361. '</div></div>';
  362. var _player = $("<div>").attr("id", "jHueTourVideoPlayer").addClass("modal").addClass("hide").addClass("fade");
  363. _player.html(_playerHTML);
  364. _player.appendTo($("body"));
  365. }
  366. else {
  367. $("#jHueTourVideoPlayer").find("h3").html(_this.currentTour.desc);
  368. $("#jHueTourVideoPlayer").find(".moreInfo").html(typeof _this.currentTour.blog != "undefined" && _this.currentTour.blog != "" ? '<a href="' + _this.currentTour.blog + '" target="_blank"><i class="fa fa-external-link"></i> ' + _this.options.labels.MORE_INFO + '</a>' : '');
  369. $("#jHueTourVideoFrame").attr("src", _this.currentTour.video + "?autoplay=1");
  370. }
  371. $("#jHueTourVideoPlayer").modal().modal("show");
  372. $("#jHueTourVideoPlayer").on("hidden", function () {
  373. $("#jHueTourVideoFrame").attr("src", "about:blank");
  374. });
  375. }
  376. else {
  377. if (_this.currentTour.steps[stepNo - 1] != null) {
  378. var _step = _this.currentTour.steps[stepNo - 1];
  379. _this.currentTour.shownStep = stepNo;
  380. var _navigation = "";
  381. if (_step.visitUrl != undefined) {
  382. location.href = _step.visitUrl;
  383. }
  384. if (_step.onShown != undefined) {
  385. window.setTimeout(_step.onShown, 10);
  386. }
  387. $(".popover").remove();
  388. $(".jHueTourExposed").removeClass("jHueTourExposed");
  389. if ($(".jHueTourExposed").css("position") == "relative") {
  390. $(".jHueTourExposed").css("position", "relative");
  391. }
  392. $("#jHueTourMask").width($(document).width()).height($(document).height()).show();
  393. var _closeBtn = $("<a>");
  394. _closeBtn.addClass("btn").addClass("btn-mini").html('<i class="fa fa-times"></i>').css("float", "right").css("margin-top", "-4px").css("margin-right", "-6px");
  395. _closeBtn.click(function () {
  396. _this.performOperation("close");
  397. });
  398. var _nextBtn = $("<a>");
  399. _nextBtn.addClass("btn").addClass("btn-mini").html('<i class="fa fa-chevron-circle-right"></i>').css("margin-top", "10px");
  400. _nextBtn.click(function () {
  401. _this.showStep(_this.currentTour.shownStep + 1);
  402. });
  403. var _prevBtn = $("<a>");
  404. _prevBtn.addClass("btn").addClass("btn-mini").html('<i class="fa fa-chevron-circle-left"></i>').css("margin-top", "10px").css("margin-right", "10px");
  405. _prevBtn.click(function () {
  406. _this.showStep(_this.currentTour.shownStep - 1);
  407. });
  408. var _arrowOn = _step.arrowOn;
  409. var _additionalContent = "";
  410. if ($(_arrowOn).length == 0 || !($(_arrowOn).is(":visible"))) {
  411. _arrowOn = "body";
  412. _additionalContent = "<b>MISSING POINTER OF STEP " + _this.currentTour.shownStep + "</b> ";
  413. }
  414. $(_arrowOn).popover('destroy').popover({
  415. title: _step.title,
  416. content: _additionalContent + _step.content + "<br/>",
  417. html: true,
  418. trigger: 'manual',
  419. placement: (_step.placement != "" && _step.placement != undefined) ? _step.placement : "left"
  420. }).popover('show');
  421. if (_step.top != undefined) {
  422. if ($.isNumeric(_step.top)) {
  423. $(".popover").css("top", ($(".popover").position().top + _step.top) + "px");
  424. }
  425. else {
  426. $(".popover").css("top", _step.top);
  427. }
  428. }
  429. if (_step.left != undefined) {
  430. if ($.isNumeric(_step.left)) {
  431. $(".popover").css("left", ($(".popover").position().left + _step.left) + "px");
  432. }
  433. else {
  434. $(".popover").css("left", _step.left);
  435. }
  436. }
  437. $(".popover-title").html(_step.title);
  438. _closeBtn.prependTo($(".popover-title"));
  439. if (_this.currentTour.shownStep > 1) {
  440. _prevBtn.appendTo($(".popover-content p"));
  441. }
  442. if (_this.currentTour.shownStep < _this.currentTour.steps.length && (_step.waitForAction == undefined || _step.waitForAction == false)) {
  443. _nextBtn.appendTo($(".popover-content p"));
  444. }
  445. // last step, mark tour/tutorial as done
  446. if ($.totalStorage !== undefined && _this.currentTour.shownStep == _this.currentTour.steps.length) {
  447. var _key = location.pathname;
  448. if (_this.currentTour.path !== undefined && _this.currentTour.path != "") {
  449. _key = _this.currentTour.path;
  450. }
  451. _key += "_" + _this.currentTour.name;
  452. var _history = $.totalStorage("jHueTourHistory");
  453. if (_history == null) {
  454. _history = {}
  455. }
  456. _history[_key] = true;
  457. $.totalStorage("jHueTourHistory", _history);
  458. }
  459. var _exposedElement = $((_step.expose != undefined && _step.expose != "" ? _step.expose : _arrowOn));
  460. if (_exposedElement.css("position") === undefined || _exposedElement.css("position") != "fixed") {
  461. _exposedElement.css("position", "relative");
  462. }
  463. _exposedElement.addClass("jHueTourExposed");
  464. }
  465. }
  466. };
  467. $[pluginName] = function (options, stepNo) {
  468. var _el = $("body");
  469. if (!$("body").data('plugin_' + pluginName)) {
  470. $("body").data('plugin_' + pluginName, new Plugin(_el, options));
  471. }
  472. if (options === undefined) {
  473. return $("body").data('plugin_' + pluginName).availableTours();
  474. }
  475. if (typeof options == "string") {
  476. if (stepNo === undefined) {
  477. $("body").data('plugin_' + pluginName).performOperation(options);
  478. }
  479. else if ($.isNumeric(stepNo)) {
  480. $("body").data('plugin_' + pluginName).showTour(options, stepNo);
  481. }
  482. }
  483. else if ($.isNumeric(options)) {
  484. $("body").data('plugin_' + pluginName).showStep(options);
  485. }
  486. else {
  487. $("body").data('plugin_' + pluginName).addTours(options);
  488. }
  489. };
  490. })(jQuery, window, document);