I am a newb to durandal and knockout. I recently started a project working with the VS2013 hot towel template. Turns out the code is somewhat out of date and I have worked through some of the initial getting started issues. I am attempting to integrate a nav menu related jquery plugin. However, I having trouble hooking up the plugin functionality with the nav menu found in my shell.html view.
here is my main.js file:
requirejs.config({
paths: {
'text': '../scripts/vendor/require/text',
'durandal': '../scripts/vendor/durandal/js',
'plugins': '../scripts/vendor/durandal/js/plugins',
'transitions': '../scripts/vendor/durandal/js/transitions',
'knockout': '../scripts/vendor/knockout/knockout-3.1.0',
'bootstrap': '../scripts/vendor/bootstrap/bootstrap',
'toastr': '../scripts/vendor/toastr/toastr',
'jquery': '../scripts/vendor/jquery/jquery-2.1.4',
'logger': 'services/logger',
'theme' : '../scripts/js/idealTheme',
'global' : '../scripts/js/functions'
},
shim: {
'bootstrap': {
deps: ['jquery'],
exports: 'jQuery'
},
'toastr': {
deps: ['jquery'],
exports: 'jQuery'
},
'theme': {
deps: ['jquery'],
exports: 'jQuery'
}
,
'global': {
deps: ['jquery'],
exports: 'jQuery'
}
}
});
define(['durandal/system', 'durandal/app', 'durandal/viewLocator', 'plugins/router', 'logger'], function (system, app, viewLocator, router, logger) {
//>>excludeStart("build", true);
system.debug(true);
//>>excludeEnd("build");
app.title = 'TestApp';
app.configurePlugins({
router: true,
dialog: true
});
app.start().then(function () {
//Replace 'viewmodels' in the moduleId with 'views' to locate the view.
//Look for partial views in a 'views' folder in the root.
viewLocator.useConvention();
//Show the app by setting the root view model for our application with a transition.
app.setRoot('viewmodels/shell', 'entrance');
// override bad route behavior to write to
// console log and show error toast
router.handleInvalidRoute = function (route, params) {
logger.logError('No route found', route, 'main', true);
};
});
});
and my shell.js
define(['durandal/system',
'plugins/router',
'durandal/app',
'toastr',
'theme',
'logger'
],
function (system, router, app, toastr, theme, logger) {
var vm = this;
var router = router;
var search = function () {
toastr.info('Search is not yet implemented...');
//app.showMessage('Search not yet implemented...');
};
var activate = function activate() {
$("#nav_menu").idealtheme();
logger.log('Loaded!', null, system.getModuleId(vm), true);
}
vm = {
router: router,
showSubMenu: showSubMenu,
search: search,
activate: activate
}
return vm;
});
relevant piece of my shell.html
<div id="main_wrapper">
<header id="site_header">
<!-- End Top Search -->
<nav id="main_nav">
<div id="nav_menu">
<span class="mobile_menu_trigger">
<a href="#" class="nav_trigger"><span></span></a>
</span>
<ul id="navy" class="clearfix" >
<li class="normal_menu mobile_menu_toggle current_page_item">
<a href="index.html"><span>Home</span></a>
<ul>
<li class="normal_menu"><a href="index.html">Home Page V1</a></li>
<li class="normal_menu"><a href="index2.html">Home Page V2</a></li>
<li class="normal_menu"><a href="index3.html">Home Page V3</a></li>
<li class="normal_menu"><a href="index4.html">Home Page V4</a></li>
<li class="normal_menu"><a href="index5.html">Home Page V5</a></li>
<li class="normal_menu">
<a href="index-one-page1.html">Home One Page </a>
<ul>
<li class="normal_menu"><a href="index-one-page1.html">Home One Page V1</a></li>
<li class="normal_menu"><a href="index-one-page2.html">Home One Page V2</a></li>
</ul>
</li>
finally, the theme plugin file
(function ($) {
//========> Menu
$.fn.idealtheme = function (options) {
var whatTheLastWidth = getScreenWidth();
var ifisdescktop = false;
var MqL = 1170;
var settings = {
duration: 300,
delayOpen: 0,
menuType: "horizontal", // horizontal - vertical
position: "right", // right - left
parentArrow: true,
hideClickOut: true,
submenuTrigger: "hover",
backText: "Back to ",
clickToltipText: "Click",
};
$.extend(settings, options);
var nav_con = $(this);
var $nav_con_parent = nav_con.parent("#main_nav");
var menu = $(this).find('#navy');
//=====> Mega Menu Top Space
function megaMenuTop() {
$(menu).find('.has_mega_menu').each(function () {
var top_space = $(this).parent('li').outerHeight();
$(this).find(' > .mega_menu').css({ "top": top_space + "px", "width": "100%" });
});
}
megaMenuTop();
//=====> Vertical and Horizontal
if (settings.menuType == "vertical") {
$(menu).addClass("vertical_menu");
if (settings.position == "right") {
$(menu).addClass("position_right");
} else {
$(menu).addClass("position_left");
}
} else {
$(menu).addClass("horizontal_menu");
}
//=====> Add Arrows To Parent li
if (settings.parentArrow === true) {
$(menu).find("li.normal_menu li, li.has_image_menu").each(function () {
if ($(this).children("ul").length > 0) {
$(this).children("a").append("<span class='parent_arrow normal_menu_arrow'></span>");
}
});
$(menu).find("ul.mega_menu li ul li, .tab_menu_list > li").each(function () {
if ($(this).children("ul").length > 0) {
$(this).children("a").append("<span class='parent_arrow mega_arrow'></span>");
}
});
}
function TopSearchFunc() {
$(".top_search").each(function (index, element) {
var top_search = $(this);
top_search.submit(function (event) {
event.stopPropagation();
if (top_search.hasClass("small_top_search")) {
top_search.removeClass("small_top_search");
top_search.addClass("large_top_search");
if (getScreenWidth() <= 315) {
top_search.siblings("#top_cart").animate({ opacity: 0 });
}
top_search.siblings("#nav_menu:not(.mobile_menu), .logo_container").animate({ opacity: 0 });
return false;
}
});
$(top_search).on("click touchstart", function (e) {
e.stopPropagation();
});
$(document).on("click touchstart", function (e) {
if (top_search.hasClass("large_top_search")) {
top_search.removeClass("large_top_search");
top_search.addClass("small_top_search");
if (getScreenWidth() <= 315) {
top_search.siblings("#top_cart").animate({ opacity: 1 });
}
top_search.siblings("#nav_menu:not(.mobile_menu), .logo_container").animate({ opacity: 1 });
}
});
});
if (getScreenWidth() < 1190) {
$("#navigation_bar").find(".top_search").addClass("small_top_search");
} else {
$("#navigation_bar").find(".top_search").removeClass("small_top_search");
}
}
var top_search_func = new TopSearchFunc();
$(window).resize(function () {
top_search_func = new TopSearchFunc();
megaMenuTop();
if (whatTheLastWidth > 992 && getScreenWidth() <= 992 && $("body").hasClass("header_on_side")) {
$(menu).slideUp();
}
if (whatTheLastWidth <= 992 && getScreenWidth() > 992 && $("body").hasClass("header_on_side")) {
$(menu).slideDown();
}
if (whatTheLastWidth <= 992 && getScreenWidth() > 992 && !$("body").hasClass("header_on_side")) {
resizeTabsMenu();
removeTrigger();
playMenuEvents();
}
if (whatTheLastWidth > 992 && getScreenWidth() <= 992) {
releaseTrigger();
playMobileEvents();
resizeTabsMenu();
$(menu).slideUp();
}
whatTheLastWidth = getScreenWidth();
return false;
});
//======> After Refresh
function ActionAfterRefresh() {
if (getScreenWidth() <= 992 || $("body").hasClass("header_on_side")) {
releaseTrigger();
playMobileEvents();
resizeTabsMenu();
} else {
resizeTabsMenu();
removeTrigger();
playMenuEvents();
}
}
var action_after_ref = new ActionAfterRefresh();
//======> Mobile Menu
function playMobileEvents() {
$(".nav_trigger").removeClass("nav-is-visible");
$(menu).find("li, a").unbind();
if ($(nav_con).hasClass("mobile_menu")) {
$(nav_con).find("li.normal_menu").each(function () {
if ($(this).children("ul").length > 0) {
$(this).children("a").not(':has(.parent_arrow)').append("<span class='parent_arrow normal_menu_arrow'></span>");
}
});
}
megaMenuEvents();
$(menu).find("li:not(.has-children):not(.go-back)").each(function () {
$(this).removeClass("opened_menu");
if ($(this).children("ul").length > 0) {
var $li_li_li = $(this);
$(this).children("a").on("click", function (event) {
var curr_act = $(this);
if (!$(this).parent().hasClass("opened_menu")) {
$(this).parent().addClass("opened_menu");
$(this).parent().siblings("li").removeClass("opened_menu");
if ($(this).parent().hasClass("tab_menu_item")) {
$(this).parent().addClass("active");
$(this).parent().siblings("li").removeClass("active");
}
$(this).siblings("ul").slideDown(settings.duration);
$(this).parent("li").siblings("li").children("ul").slideUp(settings.duration);
setTimeout(function () {
var curr_position = curr_act.offset().top;
$('body,html').animate({
//scrollTop: curr_position ,
}, { queue: false, duration: 900, easing: "easeInOutExpo" }
);
}, settings.duration);
return false;
}
else {
$(this).parent().removeClass("opened_menu");
$(this).siblings("ul").slideUp(settings.duration);
if ($li_li_li.hasClass("mobile_menu_toggle") || $li_li_li.hasClass("tab_menu_item")) {
return false;
}
}
});
}
});
}
function megaMenuEvents() {
$(menu).find('li.has_mega_menu ul').removeClass("moves-out");
$(menu).find('.go-back, .mega_toltip').remove();
$(menu).find('li.has_mega_menu > ul').hover(function () {
$(this).find(".mega_menu_in ul").each(function (index, element) {
var $mega_ul = $(this);
var its_height = 0;
$mega_ul.children('li').each(function (index, element) {
var ul_li_num = $(this).innerHeight();
its_height += ul_li_num;
});
$mega_ul.attr("data-height", its_height);
});
});
$(menu).find('ul.mega_menu li li').each(function (index, element) {
var $mega_element = $(this);
if ($mega_element.children('ul').length > 0) {
$mega_element.addClass("has-children");
$mega_element.children('ul').addClass("is-hidden");
}
});
$(menu).find('ul.mega_menu li.has-children').children('ul').each(function (index, element) {
var $mega_ul = $(this);
var its_height = 0;
$mega_ul.children('li').each(function (index, element) {
var ul_li_num = $(this).innerHeight();
its_height += ul_li_num;
});
$mega_ul.attr("data-height", its_height);
var $mega_link = $mega_ul.parent('li').children('a');
var $mega_title = $mega_ul.parent('li').children('a').text();
$("<span class='mega_toltip'>" + settings.clickToltipText + "</span>").prependTo($mega_link);
if (!$mega_link.find('.go-back').length) {
$("<li class='go-back'><a href='#'>" + settings.backText + $mega_title + "</a></li>").prependTo($mega_ul);
}
});
$(menu).find('ul.mega_menu li.has-children').children('a').on('click', function (event) {
event.preventDefault();
var selected = $(this);
if (selected.next('ul').hasClass('is-hidden')) {
var ul_height = parseInt(selected.next('ul').attr("data-height"));
var link_height = parseInt(selected.innerHeight());
var all_height = ul_height + link_height;
selected.addClass('selected').next('ul').removeClass('is-hidden').end().parent('.has-children').parent('ul').addClass('moves-out');
selected.closest('.mega_menu_in').animate({ height: all_height });
selected.parent('.has-children').siblings('.has-children').children('ul').addClass('is-hidden').end().children('a').removeClass('selected');
//====> if is mobile
if (selected.closest('#nav_menu').hasClass("mobile_menu")) {
selected.parent('.has-children').removeClass("mega_parent_hidden").prevAll('li').slideUp(settings.duration);
}
}
});
//submenu items - go back link
$('.go-back').on('click', function () {
var link_height = parseInt($(this).parent("ul").parent("li").parent("ul").attr("data-height"));
$(this).parent('ul').addClass('is-hidden').parent('.has-children').parent('ul').removeClass('moves-out');
$(this).closest('.mega_menu_in').animate({ height: link_height });
//====> if is mobile
if ($(this).closest('#nav_menu').hasClass("mobile_menu")) {
$(this).parent('ul').parent('li').removeClass("mega_parent_hidden").prevAll('li').slideDown(settings.duration);
}
return false;
});
}
//======> Desktop Menu
function playMenuEvents() {
$(menu).children('li').children('ul').hide(0);
$(menu).find("li, a").unbind();
$(menu).slideDown(settings.duration);
$(menu).find('ul.tab_menu_list').each(function (index, element) {
var tab_link = $(this).children('li').children('a');
$("<span class='mega_toltip'>" + settings.clickToltipText + "</span>").prependTo(tab_link);
$(this).children('li').on('mouseover', function () {
if (!$(this).hasClass('active')) {
$(this).children('ul').stop().fadeIn();
$(this).siblings().children('ul').stop().fadeOut();
$(this).addClass('active');
$(this).siblings().removeClass('active');
}
});
});
megaMenuEvents();
$(menu).find('li.normal_menu, > li').hover(function () {
var li_link = $(this).children('a');
$(this).children('ul').stop().fadeIn(settings.duration);
}, function () {
$(this).children('ul').stop().fadeOut(settings.duration);
});
}
//======> Trigger Button Mobile Menu
function releaseTrigger() {
$(nav_con).find(".nav_trigger").unbind();
$(nav_con).addClass('mobile_menu');
$nav_con_parent.addClass('has_mobile_menu');
$(nav_con).find('.nav_trigger').each(function (index, element) {
var $trigger_mob = $(this);
$trigger_mob.on('click touchstart', function (e) {
e.preventDefault();
if ($(this).hasClass('nav-is-visible')) {
$(this).removeClass('nav-is-visible');
$(menu).slideUp(settings.duration);
} else {
$(this).addClass('nav-is-visible');
$(document).unbind("click");
$(document).unbind("touchstart");
$(menu).slideDown(settings.duration, function () {
$(menu).on("click touchstart", function (event) {
event.stopPropagation();
});
$(document).on('click touchstart', function (event) {
if ($trigger_mob.hasClass('nav-is-visible') && getScreenWidth() <= 992) {
$trigger_mob.removeClass('nav-is-visible');
$(menu).slideUp(settings.duration);
}
});
});
}
});
});
}
//=====> get tabs menu height
function resizeTabsMenu() {
function thisHeight() {
return $(this).outerHeight();
}
$.fn.sandbox = function (fn) {
var element = $(this).clone(), result;
element.css({ visibility: 'hidden', display: 'block' }).insertAfter(this);
element.attr('style', element.attr('style').replace('block', 'block !important'));
var thisULMax = Math.max.apply(Math, $(element).find("ul:not(.image_menu)").map(thisHeight));
result = fn.apply(element);
element.remove();
return thisULMax;
};
$(".tab_menu").each(function () {
$(this).css({ "height": "inherit" });
if (!$(nav_con).hasClass("mobile_menu")) {
var height = $(this).sandbox(function () { return this.height(); });
$(this).height(height);
}
});
}
resizeTabsMenu();
//=====> End get tabs menu height
function removeTrigger() {
$(nav_con).removeClass('mobile_menu');
$nav_con_parent.removeClass('has_mobile_menu');
}
//----------> sticky menu
enar_sticky();
function getScreenWidth() {
return document.documentElement.clientWidth || document.body.clientWidth || window.innerWidth;
}
//----------> sticky menu
function enar_sticky() {
if ($.isFunction($.fn.sticky)) {
var $navigation_bar = $("#navigation_bar");
$navigation_bar.unstick();
var mobile_menu_len = $navigation_bar.find(".mobile_menu").length;
var side_header = $(".header_on_side").length;
if (mobile_menu_len === 0 && side_header === 0) {
$navigation_bar.sticky({
topSpacing: 0,
className: "sticky_menu",
getWidthFrom: "body"
});
} else {
$navigation_bar.unstick();
}
}
}
};
})( jQuery );
I am basically trying to get the plugin functionality to work with durandal and knockout. I have attempted to hook the theme function to a DOM element in my view model with no success. I have also attempted to simply place the lines of code needed to display the sub menus within my view model also with no success.
I tried creating a function to handle the mouse over event of list item elements and display the sub menus with no success. I get an error message that the "children" function is not defined. I would really like to get the entire plugin to work but will take baby steps until that is accomplished.
var showSubMenu = function (data, event) {
$parent = event.currentTarget;
$parent.children('ul').stop().fadeIn();
};
and in my shell.html attached an event binding like this:
<li data-bind="event: { mouseover: showSubMenu }" class="normal_menu mobile_menu_toggle current_page_item">
Any information or help one can provide is appreciated.
thanks,
Option 1 You could wrap the plugin in a define like this:
And then in my shell I'd "require" the plugin, which will detect that jquery is required and download that first and then run your plugin script before the durandal composition happens:
Option 2 Or you could just include jquery and other dependencies that are packaged as requirejs modules in your html file without requirejs, such as:
In your main.js file, change the requirejs config remove the paths you've now included as script tags and remove the shim setting to look like this:
Then, while still in your main.js file, define the modules that would return something from the ones you've just removed from "paths" in the requirejs config like this:
Finally, continue as you were: