When my website passes through the defined pixel threshold as stated in Tinynav.js module all the menu items appear as they should minus the parent menu items. I have defined the parent items through Special Menu Items module as no link, meaning the parent div is simply a placeholder for the child menu items and doesn't link to any content. The issue is when the website passes into the threshold and the dropdown scrollable menu appears in place of the navigational menu it doesn't show the parent items only the child items linked to the parent.
It seems because the parent item has been defined as a no link the Tiny.js module skips it and simply displays the child menu. I have tried multiple things and none have worked, I have uploaded the js associated with Tinynav.js and html so it can be edited.
It could be the editing of this file or something else.
Thanks for helping out
html
<div id="nav"><!--nav-->
<ul class="menu"><li class="first leaf">Home</li>
<li class="leaf">Public Courses</li>
<li class="expanded"><span title="" class="nolink">Tradition </span><ul class="menu"> <li class="first leaf">Egypt</li>
<li class="last leaf">Tibet</li>
</ul></li>
<li class="expanded">For Reflection<ul class="menu"><li class="first leaf">Current Reflections</li>
<li class="last leaf">Past Reflections</li>
</ul></li>
<li class="last leaf active-trail">About Us</li>
</ul>
</div><!--/nav-->
tinynav-fork.js
/*! Originally based on the tinynav.js library found at http://tinynav.viljamis.com by #viljamis */
(function ($, window, i) {
$.fn.tinyNav = function (options) {
// Default settings
var settings = $.extend({
'active' : 'selected', // String: Set the "active" class
'header' : false, // Boolean: Show header instead of the active item
'indent' : '--', // String: Set this to empty to disable identing
'depth_count' : 2 // Integer: depth to stop counting
}, options);
return this.each(function () {
// Used for namespacing
i++;
var $nav = $(this),
// Namespacing
namespace = 'tinynav',
namespace_i = namespace + i,
l_namespace_i = '.l_' + namespace_i,
$select = $('<select/>').addClass(namespace + ' ' + namespace_i);
if ($nav.is('ul,ol')) {
if (settings.header) {
$select.append(
$('<option value="-null-"/>').text(Drupal.t('Navigation'))
);
}
// Build options
var options = '';
$nav
.addClass('l_' + namespace_i)
.find('a')
.each(function () {
var indent = '';
// indent once for each parent this has
var parent_count = $(this).parents("ul,ol").length;
// apply indenting if found
for (var i=1; i<parent_count; i++) {
indent += settings.indent;
}
// add spacing to end if we indent at all
if (indent != '') {
indent += ' ';
}
if (parent_count < settings.depth_count) {
options +=
'<option value="' + $(this).attr('href') + '">' +
indent + $(this).text() +
'</option>';
}
});
// Append options into a select
$select.append(options);
// Select the active item
$select
.find(':eq(' + (settings.header + $(l_namespace_i + ' li')
.index($(l_namespace_i + ' .' + settings.active)) + ')'))
.attr('selected', true);
// Change window location
$select.change(function () {
if ($(this).val() != '-null-') {
window.location.href = $(this).val();
}
});
// Inject select
$(l_namespace_i).after($select);
}
});
};
})(jQuery, this, 0);
tinynav-drupal.js
(function ($) {
Drupal.behaviors.tinynav = {
attach: function (context, settings) {
// make sure we don't try to access an undefined array
settings.tinynav = settings.tinynav || {
selector: '#nav ul',
media_query: 'all and (max-width:795px)',
header: false,
active: 'active-trail'
}
// Add the class to the selectors so we can access it later
$(settings.tinynav.selector).addClass('tinyjs');
// Build the Settings array
var tinyNavSettings = {
header: settings.tinynav.header
};
if (settings.tinynav.active) {
tinyNavSettings.active = settings.tinynav.active;
}
// Tinynav (<-- new verb) them all
$('.tinyjs').tinyNav(tinyNavSettings);
// Add a wrapper to the select element
$('select.tinynav').wrap('<div class="tinynav-wrapper"/>');
},
weight: 99
};
})(jQuery);
The problem is in the Tinynav.js module because it's looking for an
<a>
but Special Menu Items module create a
<span>
this is a working code:
/*! http://tinynav.viljamis.com v1.03 by #viljamis
modified by l.tagliamonte to fix Special Menu Items module menu links*/
(function ($, window, i) {
$.fn.tinyNav = function (options) {
// Default settings
var settings = $.extend({
'active' : 'selected', // String: Set the "active" class
'header' : false // Boolean: Show header instead of the active item
}, options);
return this.each(function () {
// Used for namespacing
i++;
var $nav = $(this),
// Namespacing
namespace = 'tinynav',
namespace_i = namespace + i,
l_namespace_i = '.l_' + namespace_i,
$select = $('<select/>').addClass(namespace + ' ' + namespace_i);
if ($nav.is('ul,ol')) {
if (settings.header) {
$select.append(
$('<option/>').text('Navigation')
);
}
// Build options
var options = '';
var indent = 0;
var indented = [" "];
for ( var i = 0; i < 10; i++) {
indented.push(indented[indented.length-1]+indented[indented.length-1]);
}
indented[0] = "";
$nav
.addClass('l_' + namespace_i)
.children('li')
.each(buildNavTree=function () {
var a = $(this).children('a').first();
var nolink = $(this).children('span.nolink').first();
if (nolink.html() != null){
options +=
'<option value="' + nolink.html() + '" disabled>' +
indented[indent] + nolink.html() +
'</option>';
}else{
options +=
'<option value="' + a.attr('href') + '">' +
indented[indent] + a.text() +
'</option>';
}
indent++;
$(this).children('ul,ol').children('li').each(buildNavTree);
indent--;
});
// Append options into a select
$select.append(options);
// Select the active item
if (!settings.header) {
$select
.find(':eq(' + $(l_namespace_i + ' li')
.index($(l_namespace_i + ' li.' + settings.active)) + ')')
.attr('selected', true);
}
// Change window location
$select.change(function () {
window.location.href = $(this).val();
});
// Inject select
$(l_namespace_i).after($select);
}
$('option[value="'+document.location+'"]').attr("selected","selected");
});
};
})(jQuery, this, 0);
// Tinynav
jQuery(function(){
// Main Menu
jQuery('#main-menu > ul.menu').tinyNav({
active: 'selected', // Set the "active" class
});
});
Related
Hi I use Bootstrap tab menu and ShinDarth's Nestable++ menu editor
My problem live demo
$("a[data-toggle='tab']").on("shown.bs.tab", function(e) {
var hedef = $(e.target).attr("href");
alert(hedef);
if (hedef == "#footmenu") {
var nestabad = "#nestable2";
var menutip = "alt";
var nestableList = $("#nestable2 > .dd-list");
} else if (hedef == "#headmenu") {
var nestabad = "#nestable";
var menutip = "ust";
var nestableList = $("#nestable > .dd-list");
}
});
var deleteFromMenu = function () {
var targetId = $(this).data('owner-'+menutip+'-id');
alert(JSON.stringify($(this).data()));
var target = $('[data-'+menutip+'-id="' + targetId + '"]');
alert(JSON.stringify(target));
var result = confirm("Delete " + target.data('name') + " and all its subitems ?");
if (!result) {
return;
}
// Remove children (if any)
target.find("li").each(function () {
deleteFromMenuHelper($(this));
});
// Remove parent
deleteFromMenuHelper(target);
target.data();
// update JSON
updateOutput($('#nestable2').data('output', $('#json-output2')));
updateOutput($('#nestable').data('output', $('#json-output')));
};
always tabs menu change menutip variable defined.
I delete menu link before switch menu page menutip defined.
TWO confirm(1.undefined variable,2.defined variable) after menu link deleted
Help me!
I am trying to display data in a jQuery DataTable which has column level filter at the top, fixed height and scroller enabled. I am able to display the column level filter at the top and have it working. But, as soon as I set the height (scrollY property), the column level filters at the top disappear.
Fiddler: https://jsfiddle.net/8f63kmeo/6/
HTML:
<table id="CustomFilterOnTop" class="display nowrap" width="100%"></table>
JS
var Report4Component = (function () {
function Report4Component() {
//contorls
this.customFilterOnTopControl = "CustomFilterOnTop"; //table id
//data table object
this.customFilterOnTopGrid = null;
}
Report4Component.prototype.ShowGrid = function () {
var instance = this;
//create the datatable object
instance.customFilterOnTopGrid = $('#' + instance.customFilterOnTopControl).DataTable({
columns: [
{ data: "Description", title: "Desc" },
{ data: "Status", title: "Status" },
{ data: "Count", title: "Count" }
],
"paging": true,
//scrollY: "30vh",
//deferRender: true,
//scroller: true,
dom: '<"top"Bf<"clear">>rt <"bottom"<"Notes">ilp<"clear">>',
buttons: [
{
text: 'Load All',
action: function (e, dt, node, config) {
instance.ShowData(10000);
}
}
]
});
//now, add a second row in header which will hold controls for filtering.
$('#' + instance.customFilterOnTopControl + ' thead').append('<tr role="row" id="FilterRow">' +
'<th>Desc</th>' +
'<th>Status</th>' +
'<th>Count</th>' +
'</tr>');
$('#' + instance.customFilterOnTopControl + ' thead tr#FilterRow th').each(function () {
var title = $('#' + instance.customFilterOnTopControl + ' thead th').eq($(this).index()).text();
$(this).html('<input type="text" onclick="StopPropagation(event);" placeholder="Search ' + title + '" class="form-control" />');
});
$("div.Notes").html('<div class="alert alert-warning">This is a notes section part of the table dom.</div>');
};
Report4Component.prototype.BindEvents = function () {
var instance = this;
$("#CustomFilterOnTop thead input").on('keyup change', function () {
instance.customFilterOnTopGrid
.column($(this).parent().index() + ':visible')
.search(this.value)
.draw();
});
};
Report4Component.prototype.ShowData = function (limit) {
if (limit === void 0) { limit = 100; }
var instance = this;
instance.customFilterOnTopGrid.clear(); //latest api function
var recordList = [];
for (var i = 1; i <= limit; i++) {
var record = {};
record.Description = "This is a test description of record " + i;
record.Status = "Some status " + i;
record.Count = i;
recordList.push(record);
}
instance.customFilterOnTopGrid.rows.add(recordList);
instance.customFilterOnTopGrid.draw();
};
return Report4Component;
}());
$(function () {
var report4Component = new Report4Component();
report4Component.ShowGrid();
report4Component.BindEvents();
report4Component.ShowData();
});
function StopPropagation(evt) {
if (evt.stopPropagation !== undefined) {
evt.stopPropagation();
}
else {
evt.cancelBubble = true;
}
}
Current Status
When the following properties are commented,
//scrollY: "30vh",
//deferRender: true,
//scroller: true,
the table appears with the column level filters on top as shown below,
Issue:
When the above properties are enabled, the column level filter disappears,
You can use the fiddler to see this behavior.
Expectation:
I want to have a DataTable with column level filter on top, fixed height and scroller enabled. What am I missing? Any help / suggestion is appreciated.
You need to use table().header() API function to access thead element instead of referencing it directly. When Scroller or FixedHeader extensions are used thead element appears outside of your table in a separate element.
See updated jsFiddle for code and demonstration.
I'm an absolute newbie with javascript, but I'm just trying to tweak JPlayer to use an XML file for the playlist instead of the hard-coded playlist. So, here's the bit of code that creates the playlist:
//<![CDATA[
$(document).ready(function(){
var Playlist = function(instance, playlist, options) {
var self = this;
this.instance = instance; // String: To associate specific HTML with this playlist
this.playlist = playlist; // Array of Objects: The playlist
this.options = options; // Object: The jPlayer constructor options for this playlist
this.current = 0;
this.cssId = {
jPlayer: "jquery_jplayer_",
interface: "jp_interface_",
playlist: "jp_playlist_"
};
this.cssSelector = {};
$.each(this.cssId, function(entity, id) {
self.cssSelector[entity] = "#" + id + self.instance;
});
if(!this.options.cssSelectorAncestor) {
this.options.cssSelectorAncestor = this.cssSelector.interface;
}
$(this.cssSelector.jPlayer).jPlayer(this.options);
$(this.cssSelector.interface + " .jp-previous").click(function() {
self.playlistPrev();
$(this).blur();
return false;
});
$(this.cssSelector.interface + " .jp-next").click(function() {
self.playlistNext();
$(this).blur();
return false;
});
};
Playlist.prototype = {
displayPlaylist: function() {
var self = this;
$(this.cssSelector.playlist + " ul").empty();
for (i=0; i < this.playlist.length; i++) {
var listItem = (i === this.playlist.length-1) ? "<li class='jp-playlist-last'>" : "<li>";
listItem += "<a href='#' id='" + this.cssId.playlist + this.instance + "_item_" + i +"' tabindex='1'>"+ this.playlist[i].name +"</a>";
// Create links to free media
if(this.playlist[i].free) {
var first = true;
listItem += "<div class='jp-free-media'>(";
$.each(this.playlist[i], function(property,value) {
if($.jPlayer.prototype.format[property]) { // Check property is a media format.
if(first) {
first = false;
} else {
listItem += " | ";
}
listItem += "<a id='" + self.cssId.playlist + self.instance + "_item_" + i + "_" + property + "' href='" + value + "' tabindex='1'>" + property + "</a>";
}
});
listItem += ")</span>";
}
listItem += "</li>";
// Associate playlist items with their media
$(this.cssSelector.playlist + " ul").append(listItem);
$(this.cssSelector.playlist + "_item_" + i).data("index", i).click(function() {
var index = $(this).data("index");
if(self.current !== index) {
self.playlistChange(index);
} else {
$(self.cssSelector.jPlayer).jPlayer("play");
}
$(this).blur();
return false;
});
// Disable free media links to force access via right click
if(this.playlist[i].free) {
$.each(this.playlist[i], function(property,value) {
if($.jPlayer.prototype.format[property]) { // Check property is a media format.
$(self.cssSelector.playlist + "_item_" + i + "_" + property).data("index", i).click(function() {
var index = $(this).data("index");
$(self.cssSelector.playlist + "_item_" + index).click();
$(this).blur();
return false;
});
}
});
}
}
},
playlistInit: function(autoplay) {
if(autoplay) {
this.playlistChange(this.current);
} else {
this.playlistConfig(this.current);
}
},
playlistConfig: function(index) {
$(this.cssSelector.playlist + "_item_" + this.current).removeClass("jp-playlist-current").parent().removeClass("jp-playlist-current");
$(this.cssSelector.playlist + "_item_" + index).addClass("jp-playlist-current").parent().addClass("jp-playlist-current");
this.current = index;
$(this.cssSelector.jPlayer).jPlayer("setMedia", this.playlist[this.current]);
},
playlistChange: function(index) {
this.playlistConfig(index);
$(this.cssSelector.jPlayer).jPlayer("play");
},
playlistNext: function() {
var index = (this.current + 1 < this.playlist.length) ? this.current + 1 : 0;
this.playlistChange(index);
},
playlistPrev: function() {
var index = (this.current - 1 >= 0) ? this.current - 1 : this.playlist.length - 1;
this.playlistChange(index);
}
};
var mediaPlaylist = new Playlist("1", [
{
name:"song1",
mp3: "song1.mp3",
poster: "http://www.jplayer.org/video/poster/Incredibles_Teaser_640x272.png"
},
{
name:"song2",
mp3: "song2.mp3",
poster: "http://www.jplayer.org/video/poster/Incredibles_Teaser_640x272.png"
},
{
name:"song3",
mp3: "song3.mp3",
poster: "http://www.jplayer.org/video/poster/Incredibles_Teaser_640x272.png"**
}
], {
ready: function() {
mediaPlaylist.displayPlaylist();
mediaPlaylist.playlistInit(false); // Parameter is a boolean for autoplay.
},
ended: function() {
mediaPlaylist.playlistNext();
},
swfPath: "js",
supplied: "ogv, m4v, oga, mp3"
});
});
The part that starts with "var mediaPlaylist" is the only section i need to change. Instead of having the keys/values as name: songname, mp3: mp3, etc., I want it to pull these values from an XML file, or better yet, just push them into the array from an XML that looks like:
<song>songname</song>
<mp3>file.mp3</mp3>
The thing that's mostly confusing me here is how that function/array is set up...too many curly braces and brackets to wrap my head around. How do I get into this without breaking it?
If you have a server side XML playlist file in the form:
<?xml version="1.0" encoding="UTF-8"?>
<playlist>
<entry>
<song>songname</song>
<mp3>file.mp3</mp3>
</entry>
<entry>
<song>songname2</song>
<mp3>file2.mp3</mp3>
</entry>
</playlist>
then you can load this from the client via AJAX and create a JSON array from the XML. The following example uses the jQuery JavaScript framework and also the jquery-json plugin (only to format the Array nicely as JSON). You should be able to run this on Chrome or Firefox as both have a console (accessed by pressing F12 in the browser) in which I am outputting the JSON array.
Note: You could change console.log(...) to alert(...) if you don't use either of those browsers.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Title</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js"></script>
<script type="text/javascript" src="http://jquery-json.googlecode.com/files/jquery.json-2.2.min.js"></script>
<script type="text/javascript">
$(function() {
var Playlist = function(instance, playlist, options) {
var self = this;
this.instance = instance;
this.playlist = playlist;
this.options = options;
};
function getPlaylist(callback) {
var songs = new Array();
$.ajax({
dataType:'xml',
url:'songs.xml',
success : function (xml) {
$(xml).find('entry').each(function() {
songs.push({name: $(this).find('song').text(), mp3: $(this).find('mp3').text()});
});
callback(songs);
}
});
}
getPlaylist(function(songs) {
var playlistFromXML = jQuery.toJSON(songs);
var mediaPlaylist = new Playlist('1', playlistFromXML, null);
console.log(mediaPlaylist);
// etc...
});
});
</script>
</head><body></body></html>
Credit to How to return an array from jQuery ajax success function properly? answer as I used that approach to return a value from the jQuery $.ajax success method.
You will have to refactor the var mediaPlaylist = new Playlist() constructor into the callback function though as you cannot return the playlist value from the callback (see How can I catch the return value from the result() callback function that I'm using?)
Hope this helps :-)
How can I insert text/code at the cursors place in a div created by NicEdit?
I've tried to read the documentation and create my own plugin, but I want it to work without the tool bar (Modal Window)
This is a quick solution and tested in firefox only. But it works and should be adaptable for IE and other browsers.
function insertAtCursor(editor, value){
var editor = nicEditors.findEditor(editor);
var range = editor.getRng();
var editorField = editor.selElm();
editorField.nodeValue = editorField.nodeValue.substring(0, range.startOffset) +
value +
editorField.nodeValue.substring(range.endOffset, editorField.nodeValue.length);
}
Insert Html Plugin
Don't know if this will help or not, but this is the plugin I created for inserting Html at the cursor position. The button opens a content pane and I just paste in the html I want and submit. Works for me!
var nicMyhtmlOptions = {
buttons : {
'html' : {name : 'Insert Html', type : 'nicMyhtmlButton'}
},iconFiles : {'html' : '/nicedit/html_add.gif'}
};
var nicMyhtmlButton=nicEditorAdvancedButton.extend({
addPane: function () {
this.addForm({
'': { type: 'title', txt: 'Insert Html' },
'code' : {type : 'content', 'value' : '', style : {width: '340px', height : '200px'}}
});
},
submit : function(e) {
var mycode = this.inputs['code'].value;
this.removePane();
this.ne.nicCommand('insertHTML', mycode );
}
});
nicEditors.registerPlugin(nicPlugin,nicMyhtmlOptions);
I used the html_add icon from Silk Icons, pasted onto a transparent 18 x 18 and saved as gif in the same folder as nicEditorIcons.gif.
It works for me when I use:
function neInsertHTML(value){
$('.nicEdit-main').focus(); // Without focus it wont work!
// Inserts into first instance, you can replace it by nicEditors.findEditor('ID');
myNicEditor.nicInstances[0].nicCommand('InsertHTML', value);
}
The way I solved this was to make the nicEdit Instance div droppable, using jQuery UI; but to also make all of the elements within the div droppable.
$('div.nicEdit-main').droppable({
activeClass: 'dropReady',
hoverClass: 'dropPending',
drop: function(event,ui) {
$(this).find('.cursor').removeClass('cursor');
},
over: function(event, ui) {
if($(this).find('.cursor').length == 0) {
var insertEl = $('<span class="cursor"/>').append($(ui.draggable).attr('value'));
$(this).append(insertEl);
}
},
out: function(event, ui) {
$(this).find('.cursor').remove();
},
greedy: true
});
$('div.nicEdit-main').find('*').droppable({
activeClass: 'dropReady',
hoverClass: 'dropPending',
drop: function(event,ui) {
$(this).find('.cursor').removeClass('cursor');
},
over: function(event, ui) {
var insertEl = $('<span class="cursor"/>').append($(ui.draggable).attr('value'));
$(this).append(insertEl);
},
out: function(event, ui) {
$(this).find('.cursor').remove();
},
greedy: true
});
Then make your code or text draggable:
$('.field').draggable({
appendTo: '.content', //This is just a higher level DOM element
revert: true,
cursor: 'pointer',
zIndex: 1500, // Make sure draggable drags above everything else
containment: 'DOM',
helper: 'clone' //Clone it while dragging (keep original intact)
});
Then finally make sure you set the value of the draggable element to what you want to insert, and/or modify the code below to insert the element (span) of your choice.
$sHTML .= "<div class='field' value='{{".$config[0]."}}'>".$config[1]."</div>";
A response to #Reto: This code works, I just needed to add some fix because it doesn't insert anything if text area is empty. Also it adds only plain text. Here is the code if anybody need it:
if(editorField.nodeValue==null){
editor.setContent('<strong>Your content</strong>');
}else{
editorField.nodeValue = editorField.nodeValue.substring(0, range.startOffset) +
'<strong>Your content</strong>' +
editorField.nodeValue.substring(range.endOffset, editorField.nodeValue.length);
editor.setContent(editorField.nodeValue);
}
Change follwoing in NicEdit.js File
Updated from Reto Aebersold Ans It will handle Null Node exception, if text area is empty
update: function (A) {
(this.options.command);
if (this.options.command == 'InsertBookmark') {
var editor = nicEditors.findEditor("cpMain_area2");
var range = editor.getRng();
var editorField = editor.selElm();
// alert(editorField.content);
if (editorField.nodeValue == null) {
// editorField.setContent('"' + A + '"')
var oldStr = A.replace("<<", "").replace(">>", "");
editorField.setContent("<<" + oldStr + ">>");
}
else {
// alert('Not Null');
// alert(editorField.nodeValue + ' ' + A);
editorField.nodeValue = editorField.nodeValue.substring(0, range.startOffset) + A + editorField.nodeValue.substring(range.endOffset, editorField.nodeValue.length);
}
}
else {
// alert(A);
/* END HERE */
this.ne.nicCommand(this.options.command, A);
}
This function work when nicEdit textarea is empty or cursor is in the blank or new line.
function addToCursorPosition(textareaId,value) {
var editor = nicEditors.findEditor(textareaId);
var range = editor.getRng();
var editorField = editor.selElm();
var start = range.startOffset;
var end = range.endOffset;
if (editorField.nodeValue != null) {
editorField.nodeValue = editorField.nodeValue.substring(0, start) +
value +
editorField.nodeValue.substring(end, editorField.nodeValue.length);
}
else {
var content = nicEditors.findEditor(textareaId).getContent().split("<br>");
var linesCount = 0;
var before = "";
var after = "";
for (var i = 0; i < content.length; i++) {
if (linesCount < start) {
before += content[i] + "<br>";
}
else {
after += content[i] + "<br>";
}
linesCount++;
if (content[i]!="") {
linesCount++;
}
}
nicEditors.findEditor(textareaId).setContent(before + value + after);
}
}
I have quickly coded up a sort of product display thing that gets half of its input from the page, and the other half from an AJAX query.
Here is the code...
function productDisplay() {
products = [];
this.index = 0;
setupProductDisplay();
processListItems();
showProduct();
function setupProductDisplay() {
var productInfoBoxHtml = '<div id="product-info"><h3 class="hide-me"></h3><span id="dimensions" class="hide-me"></span><div id="product-gallery"><img alt="" src="" /></div><ul id="product-options" class="hide-me"><li id="spex-sheet">Download full spex sheet</li><li id="enlarge-image">Enlarge image</li></ul><div id="product-description" class="hide-me"></div><span id="top"></span><span id="bottom"></span><span id="side"></span><span class="loading"></span></div>';
$('#products').after(productInfoBoxHtml);
}
function processListItems() {
$('#products > li')
.append('<span class="product-view">View</span>')
.filter(':even')
.addClass('even')
.end()
.each(function() {
products.push({
id: $(this).find('h3').html(),
title: $(this).find('h3').html(),
dimensions: $(this).find('.dimensions').html(),
description: $(this).find('.product-description').html()
});
})
.find('.product-view')
.click(function() {
var $thisListItem = $(this).parents('ul li');
var index = $('#products > li').index($thisListItem);
this.index = index;
showProduct();
});
};
function showProduct() {
var index = this.index;
console.log('INDEX = ' + index);
// hide current data
$('#product-info')
.show()
.find('.hide-me, #product-gallery')
.hide()
.parent()
.find('.loading')
.show();
// get data contained in the page
$('#product-info')
.find('h3')
.html(products[index].title)
.parent()
.find('#dimensions')
.html(products[index].dimensions)
.parent()
.find('#product-description')
.html(products[index].description)
// get id & then product extra info
var id = $('#products > li').eq(index).attr('id').replace(/id-/, '');
var downloadPath = PATH_BASE + 'downloads/';
var imagePath = PATH_BASE + 'images/products/'
$.getJSON(PATH_BASE + 'products/get/' + id + '/',
function(data){
var file = '';
var images = [];
file = data.file;
images = data.images;
// show file list item if there is a file
if (file) {
$('#spex-sheet').show().find('a').attr( { href: downloadPath + file } );
} else {
$('#spex-sheet').hide();
}
// image gallery
if (images.length != 0) {
$('#product-gallery').show();
// preload image thumbnails
$.each(images, function(i, image){
var img = new Image();
img.src = imagePath + 'thumb-' + image;
img = null;
});
// set first image thumbail and enlarge link
if (images[0]) {
$('#enlarge-image').show().find('a').attr({ href: imagePath + images[0] });
$('#product-gallery img').attr ( { src: imagePath + 'thumb-' + images[0]} )
}
console.log(images);
// setup gallery
var currentImage = 0;
clearInterval(cycle);
console.log(cycle);
var cycle = setInterval(function() {
console.log(currentImage + ' = ' + index);
if (currentImage == images.length - 1) {
currentImage = 0;
} else {
currentImage ++;
};
var obj = $('#product-gallery');
var imageSource = imagePath + 'thumb-' + images[currentImage];
obj.css('backgroundImage','url(' + imageSource +')');
obj.find('img').show().fadeOut(500, function() { $(this).attr({src: imageSource}) });
$('#enlarge-image a').attr({ href: imagePath + images[currentImage] });
}, 5000);
// setup lightbox
$("#enlarge-image a").slimbox({/* Put custom options here */}, null, function(el) {
return (this == el) || ((this.rel.length > 8) && (this.rel == el.rel));
});
} else {
// no images
$('#enlarge-image').hide();
$('#product-gallery').hide();
};
// show the product info
$('#product-info')
.find('.hide-me')
.remove('#product-gallery, #spex-sheet')
.show()
.parent()
.find('.loading')
.hide();
});
};
};
The important function is showProduct(). Now generally I don't write JS like this, but I decided to give it a go. My problem is, that when a user clicks a 'more' button, and it displays the prouduct, it doesn't reset the simple slideshow (the images var is reset, I think it has to do with the setInterval() maybe, or it seems it's making a new instance of showProduct() everytime).
Does anyone know what I'm doing wrong?
I had to reformat your code to really understand what was going on. Anyway, I found the problem with the code.
As you guessed correctly, problem is with the scope but not with the variable 'images' but with variable 'cycle'. Why?
This line
var cycle = setInterval(function() {
Always creates a new local cycle variable (notice the 'var') which is not accessible when showProduct gets called the second time. This means that this line
clearInterval(cycle);
is essentially useless as it always passes null to the clearInterval function and doesn't clear anything. This means that as you keep clicking on 'more', you are creating more and more setInterval function calls, never clearing the old ones.
Anyway, I have refactored your code a little bit, I think this should work as expected. The changes I did are:
Removed this.index variable. It's better to pass 'index' to showProduct instead of setting this.index before showProduct method call and making showProduct use that variable. Also, why did you prefix the variable with 'this'?
Declared cycler variable outside the scope of showProduct, local to the productDisplay method. This insures that you can access cycler during different showProduct calls.
Created smaller functions named showFile, showGallery, showProductInfo to make it easier to understand/maintain code.
Let me know if you have any questions OR if the code still doesn't work.
function productDisplay() {
//Instead of keeping this.index variable, it's better to make showProduct function
//take index variable.
products = [];
setupProductDisplay();
processListItems();
//We have to define cycler outside the showProduct function so that it's maintained
//in between showProduct calls.
var cycler = null;
showProduct(0);
function setupProductDisplay()
{
var productInfoBoxHtml = '<div id="product-info"><h3 class="hide-me"></h3><span id="dimensions" class="hide-me"></span><div id="product-gallery"><img alt="" src="" /></div><ul id="product-options" class="hide-me"><li id="spex-sheet">Download full spex sheet</li><li id="enlarge-image">Enlarge image</li></ul><div id="product-description" class="hide-me"></div><span id="top"></span><span id="bottom"></span><span id="side"></span><span class="loading"></span></div>';
$('#products').after(productInfoBoxHtml);
}
function processListItems()
{
$('#products > li')
.append('<span class="product-view">View</span>')
.filter(':even')
.addClass('even')
.end()
.each(
function()
{
products.push({
id: $(this).find('h3').html(),
title: $(this).find('h3').html(),
dimensions: $(this).find('.dimensions').html(),
description: $(this).find('.product-description').html()
});
})
.find('.product-view')
.click( function()
{
var $thisListItem = $(this).parents('ul li');
showProduct($('#products > li').index($thisListItem));
}
);
};
function showFile(file)
{
if (file)
{
$('#spex-sheet').show().find('a').attr( { href: downloadPath + file } );
}
else
{
$('#spex-sheet').hide();
}
}
function showGallery(images)
{
if(! images || !images.length || images.length == 0)
{
$('#enlarge-image').hide();
$('#product-gallery').hide();
return;
}
$('#product-gallery').show();
$.each(images,
function(i, image)
{
var img = new Image();
img.src = imagePath + 'thumb-' + image;
img = null;
});
// set first image thumbail and enlarge link
if (images[0])
{
$('#enlarge-image').show().find('a').attr({ href: imagePath + images[0] });
$('#product-gallery img').attr ( { src: imagePath + 'thumb-' + images[0]} )
}
var currentImage = 0;
clearInterval(cycler);
cycler = setInterval(
function()
{
currentImage = currentImage == images.length - 1 ? 0 : currentImage++;
var obj = $('#product-gallery');
var imageSource = imagePath + 'thumb-' + images[currentImage];
obj.css('backgroundImage','url(' + imageSource +')');
obj.find('img').show().fadeOut(500, function() { $(this).attr({src: imageSource}) });
$('#enlarge-image a').attr({ href: imagePath + images[currentImage] });
}, 5000);
$("#enlarge-image a").slimbox({/* Put custom options here */}, null, function(el) {
return (this == el) || ((this.rel.length > 8) && (this.rel == el.rel));
});
};
function showProductInfo()
{
$('#product-info')
.find('.hide-me')
.remove('#product-gallery, #spex-sheet')
.show()
.parent()
.find('.loading')
.hide();
}
function showProduct(index)
{
$('#product-info')
.show()
.find('.hide-me, #product-gallery')
.hide()
.parent()
.find('.loading')
.show();
// get data contained in the page
$('#product-info')
.find('h3')
.html(products[index].title)
.parent()
.find('#dimensions')
.html(products[index].dimensions)
.parent()
.find('#product-description')
.html(products[index].description)
// get id & then product extra info
var id = $('#products > li').eq(index).attr('id').replace(/id-/, '');
var downloadPath = PATH_BASE + 'downloads/';
var imagePath = PATH_BASE + 'images/products/'
$.getJSON(PATH_BASE + 'products/get/' + id + '/',
function(data)
{
showFile(data.file);
showGallery(data.image);
showProductInfo();
});
};
};
If you don't define your variables with var (e.g. var images = ...;) then they will be considered global variables (members of the window object).
If you define them with var then they are visible to the whole function (even before the variable is declared) they are declared in.
I can't immediately see what the problem is, but I would recommend minimizing the scope of your variables - if they don't need to be global then make sure they aren't global.