Ok, this is a bit special. We are using UIkit in our XPages application. We also use the tabs and switcher component (http://getuikit.com/docs/tab.html and http://getuikit.com/docs/switcher.html).
They work fine until we do a partial refresh of the page. The reason is that those components are initiliazed only once after the pages is loaded. This happens directly in the lib we bind to the page - no own init script etc.
After the refresh I must re-init the whole stuff - but I am not familiar with the syntax or even the possibilities.
I searched the UIkit lib though and found something like this:
(function(UI) {
"use strict";
UI.component('tab', {
defaults: {
'target' : '>li:not(.uk-tab-responsive, .uk-disabled)',
'connect' : false,
'active' : 0,
'animation' : false,
'duration' : 200
},
boot: function() {
// init code
UI.ready(function(context) {
UI.$("[data-uk-tab]", context).each(function() {
var tab = UI.$(this);
if (!tab.data("tab")) {
var obj = UI.tab(tab, UI.Utils.options(tab.attr("data-uk-tab")));
}
});
});
},
init: function() {
var $this = this;
this.current = false;
this.on("click.uikit.tab", this.options.target, function(e) {
e.preventDefault();
if ($this.switcher && $this.switcher.animating) {
return;
}
var current = $this.find($this.options.target).not(this);
current.removeClass("uk-active").blur();
$this.trigger("change.uk.tab", [UI.$(this).addClass("uk-active"), $this.current]);
$this.current = UI.$(this);
// Update ARIA
if (!$this.options.connect) {
current.attr('aria-expanded', 'false');
UI.$(this).attr('aria-expanded', 'true');
}
});
if (this.options.connect) {
this.connect = UI.$(this.options.connect);
}
// init responsive tab
this.responsivetab = UI.$('<li class="uk-tab-responsive uk-active"><a></a></li>').append('<div class="uk-dropdown uk-dropdown-small"><ul class="uk-nav uk-nav-dropdown"></ul><div>');
this.responsivetab.dropdown = this.responsivetab.find('.uk-dropdown');
this.responsivetab.lst = this.responsivetab.dropdown.find('ul');
this.responsivetab.caption = this.responsivetab.find('a:first');
if (this.element.hasClass("uk-tab-bottom")) this.responsivetab.dropdown.addClass("uk-dropdown-up");
// handle click
this.responsivetab.lst.on('click.uikit.tab', 'a', function(e) {
e.preventDefault();
e.stopPropagation();
var link = UI.$(this);
$this.element.children('li:not(.uk-tab-responsive)').eq(link.data('index')).trigger('click');
});
this.on('show.uk.switcher change.uk.tab', function(e, tab) {
$this.responsivetab.caption.html(tab.text());
});
this.element.append(this.responsivetab);
// init UIkit components
if (this.options.connect) {
this.switcher = UI.switcher(this.element, {
"toggle" : ">li:not(.uk-tab-responsive)",
"connect" : this.options.connect,
"active" : this.options.active,
"animation" : this.options.animation,
"duration" : this.options.duration
});
}
UI.dropdown(this.responsivetab, {"mode": "click"});
// init
$this.trigger("change.uk.tab", [this.element.find(this.options.target).filter('.uk-active')]);
this.check();
UI.$win.on('resize orientationchange', UI.Utils.debounce(function(){
if ($this.element.is(":visible")) $this.check();
}, 100));
this.on('display.uk.check', function(){
if ($this.element.is(":visible")) $this.check();
});
},
check: function() {
var children = this.element.children('li:not(.uk-tab-responsive)').removeClass('uk-hidden');
if (!children.length) return;
var top = (children.eq(0).offset().top + Math.ceil(children.eq(0).height()/2)),
doresponsive = false,
item, link;
this.responsivetab.lst.empty();
children.each(function(){
if (UI.$(this).offset().top > top) {
doresponsive = true;
}
});
if (doresponsive) {
for (var i = 0; i < children.length; i++) {
item = UI.$(children.eq(i));
link = item.find('a');
if (item.css('float') != 'none' && !item.attr('uk-dropdown')) {
item.addClass('uk-hidden');
if (!item.hasClass('uk-disabled')) {
this.responsivetab.lst.append('<li>'+link.html()+'</li>');
}
}
}
}
this.responsivetab[this.responsivetab.lst.children('li').length ? 'removeClass':'addClass']('uk-hidden');
}
});
})(UIkit);
Similar code is created for the connected switcher component.
You can see a demo of my problem here: http://notesx.net/customrenderer.nsf/demo.xsp
Source code here: https://github.com/zeromancer1972/CustomRendererDemo/blob/master/ODP/XPages/demo.xsp
As this is part of the library itself I'd like to find a way to call this from outside the library.
Any ideas are highly appreciated!
Newer versions of uikit have an init method, upgrade and call it from the onComplete event of the combo box.
<xp:comboBox
id="comboBox1">
<xp:selectItems>
<xp:this.value><![CDATA[#{javascript:return ["value 1", "value 2"];}]]></xp:this.value>
</xp:selectItems>
<xp:eventHandler
event="onchange"
submit="true"
refreshMode="partial"
refreshId="page"
onComplete="$.UIkit.init();">
</xp:eventHandler>
</xp:comboBox>
Related
Overview
So I'm trying to take functionality from one part of Squarespace's Galapagos commerce template and add it to another but it's proving to be more difficult than I thought.
I need the image-swapping capability of the "Quick View" (example - mouse over any image and click Quick View ) to replace the column of full sized zoomable images in the "Product View" (example - you see this once you click on a product).
So I found the code for each section:
Product View
This code simply goes through each image in the array and spits it out with the id jsProductItemImages which allows it to be hovered and zoomed.
<div class="productitem-images" id="jsProductItemImages">
{.repeated section items}
{.image?}
<div class="productitem-image-zoom-wrapper sqs-image-zoom-area"><img data-load="false" class="productitem-image loading" {#|image-meta} /></div>
{.end}
{.video?}
{#|video}
{.end}
{.end}
</div>
Quick View
I'm not 100% on the logic here, but essentially it's grabbing the first image and making it a hover/zoomable primary image then listing the entire array of images beneath it as thumbnails. I read that the # symbol is the equivalent of saying this in javascript, but I don't get why it's being used to spit out only the first image in the array.
<figure class="ProductItem-gallery">
{.section items}
<div class="ProductItem-gallery-slides">
{.repeated section #}
{.image?}
<div class="ProductItem-gallery-slides-item" data-slide-index="{#index}"><img class="ProductItem-gallery-slides-item-image" data-load="false" {#|image-meta} /></div>
{.end}
{.video?}
{#|video}
{.end}
{.end}
</div>
{.end}
<div class="ProductItem-gallery-thumbnails">
{.if items.1}{.repeated section items}<div class="ProductItem-gallery-thumbnails-item"><img class="ProductItem-gallery-thumbnails-item-image" data-load="false" {#|image-meta} /></div>{.end}{.end}
</div>
</figure>
Associated JS
First off, it should be noted that I went through and console logged every function to see what was giving the Quick View it's functionality - to no avail. Which is subsequently why I'm here. So it's easy to see where the zoom function is originating: the Product View in the Galapagos.ProductItem function on line 103 $imageContainer = Y.one('#jsProductItemImages');
But I don't see anything out of the ordinary pop up when I look at the Quick View. I've got to be missing something!
var Galapagos = {};
Y.use('node', function(Y) {
Galapagos.Site = (function(){
console.log("Galapagos.Site");
var $productPage;
function init() {
console.log("Galapagos.Site init()");
$productPage = Y.one('.collection-type-products');
if( $productPage && $productPage.hasClass('view-list') ) Galapagos.ProductList.init();
if( $productPage && $productPage.hasClass('view-item') ) Galapagos.ProductItem.init();
addDesktopTouchscreenClass();
addMediaQueryBreakpointClass();
bindEventListeners();
}
function addDesktopTouchscreenClass() {
console.log("Galapagos.Site addDesktopTouchscreenClass()");
if (Y.one('html').hasClass('touch')) {
var mousemoveDetection = Y.on('mousemove', function(){
Y.one('body').addClass('galapagos-desktop-touchscreen');
mousemoveDetection.detach();
});
}
}
function addMediaQueryBreakpointClass() {
console.log("Galapagos.Site addMediaQueryBreakpointClass()");
if( document.documentElement.clientWidth <= 724 ) {
if (Y.one('.catnav-container')) Y.one('.nav-container').prepend(Y.one('.catnav-list'));
Y.one('html').addClass('tablet-breakpoint-mixin');
} else {
if (Y.one('.catnav-container')) Y.one('.catnav-container').prepend(Y.one('.catnav-list'));
Y.one('html').removeClass('tablet-breakpoint-mixin');
}
}
function bindEventListeners() {
console.log("Galapagos.Site bindEventListeners()");
Y.on('resize', addMediaQueryBreakpointClass);
}
function getDocWidth() {
console.log("Galapagos.Site getDocWidth()");
return Y.one(document).get('docWidth');
}
function getDocHeight() {
console.log("Galapagos.Site getDocHeight()");
return Y.one(document).get('docHeight');
}
return {
init:init,
getDocWidth: getDocWidth,
getDocHeight: getDocHeight
}
}());
Galapagos.TweakListener = (function(){
console.log("Galapagos.TweakListener");
function listen(tweakName, callBack) {
if (Y.Global) {
Y.Global.on('tweak:change', Y.bind(function(f){
if ((f.getName() == tweakName) && (typeof callBack === 'function')) {
callBack(f.getValue());
}
}));
}
}
return {
listen:listen
}
}());
Galapagos.ProductItem = (function(){
console.log("Galapagos.ProductItem");
var cat;
var $imageContainer;
var $images;
var imageZoomInstances = [];
function init() {
console.log("Galapagos.ProductItem init()");
cat = Y.QueryString.parse(location.search.substring(1)).category;
$imageContainer = Y.one('#jsProductItemImages');
$images = $imageContainer.all('img[data-src]');
if ( cat ) setCatCrumb();
loadProductDetailImages();
bindEventListeners();
bindTweakListeners();
buildProductDetailImagesLightbox();
}
function bindEventListeners() {
console.log("Galapagos.ProductItem bindEventListeners()");
Y.on('resize', function(){
loadProductDetailImages();
});
}
function setCatCrumb() {
console.log("Galapagos.ProductItem setCatCrumb()");
var $catCrumb = Y.one('#jsCategoryCrumb');
var $catCrumbLink = $catCrumb.one('a');
var catCrumbHref = $catCrumbLink.getAttribute('href');
//var $mobileCatCrumbLink = Y.one('#jsMobileCategoryCrumb');
$catCrumbLink.set('text', cat).setAttribute('href', catCrumbHref + '?category=' + encodeURIComponent(cat));
//$mobileCatCrumbLink.setAttribute('href', catCrumbHref + '?category=' + encodeURIComponent(cat));
$catCrumb.removeClass('galapagos-display-none');
}
function loadProductDetailImages() {
console.log("Galapagos.ProductItem loadProductDetailImages()");
var imageZoomEnabled = Y.one('.tweak-product-item-image-zoom-enabled');
$images.each(function(image) {
ImageLoader.load(image.removeAttribute('data-load'), { load:true });
if (imageZoomEnabled) {
image.on('load', function() {
instantiateImageZoom(image);
});
}
});
}
function instantiateImageZoom(image) {
console.log("Galapagos.ProductItem instantiateImageZoom()");
imageZoomInstances.push(new Y.Squarespace.ImageZoom({
host: image.get('parentNode'),
behavior: 'hover',
zoom: parseFloat(Y.Squarespace.Template.getTweakValue('tweak-product-item-image-zoom-factor'))
}));
}
function destroyImageZoomInstances() {
console.log("Galapagos.ProductItem destroyImageZoomInstances()");
if (!imageZoomInstances || imageZoomInstances.length < 1) {
return;
}
Y.Array.each(imageZoomInstances, function(zoomInstance){
zoomInstance.destroy(true);
});
}
function buildProductDetailImagesLightbox() {
console.log("Galapagos.ProductItem buildProductDetailImagesLightbox()");
if ($images.size() >= 1 ) {
var lightboxSet = [];
$images.each(function(image) {
lightboxSet.push({
content: image
});
});
// Only show controls for size > 1
var hasControls = $images.size() > 1;
$imageContainer.delegate('click', function(e) {
var lightbox = new Y.Squarespace.Lightbox2({
controls: {
previous: hasControls,
next: hasControls
},
set: lightboxSet,
currentSetIndex: $images.indexOf(e.target)
});
lightbox.render();
}, 'img', this);
}
}
function bindTweakListeners() {
console.log("Galapagos.ProductItem bindTweakListeners()");
if (Y.Global) {
Y.Global.on('tweak:close', function() {
if (Y.one('.collection-type-products.view-item')) {
destroyImageZoomInstances();
if (Y.one('.tweak-product-item-image-zoom-enabled')) {
$images.each(function(image){
instantiateImageZoom(image);
});
}
}
}, this);
}
}
return {
init:init
}
}());
Galapagos.ProductList = (function(){
console.log("Galapagos.ProductList");
var $catNav,
$productGrid,
$productGridOrphans,
$productGridImages,
$orphanProducts,
productCount,
maxGridUnit,
orphanProductCount,
isGridBuilt;
function init() {
console.log("Galapagos.ProductList init()");
$catNav = Y.one('#jsCatNav');
$productGrid = Y.one('#jsProductGrid');
$productGridOrphans = Y.one('#jsProductGridOrphans');
if (!Y.UA.mobile && Y.one('.show-alt-image-on-hover:not(.product-info-style-overlay)')) {
$productGridImages = $productGrid.all('img[data-src]');
} else {
$productGridImages = $productGrid.all('img.productlist-image--main[data-src]');
}
productCount = $productGrid.all('.productlist-item').size();
maxGridUnit = 8;
orphanProductCount;
isGridBuilt = false;
bindEventListeners();
bindTweakListeners();
if($catNav) setActiveCategory();
if(Y.one('body').hasClass('product-grid-style-organic')) {
buildGrid();
} else {
$productGrid.removeClass('loading').removeClass('loading-height');
loadGridImages($productGridImages);
}
}
function bindEventListeners() {
console.log("Galapagos.ProductList bindEventListeners()");
Y.on('resize', function(){
loadGridImages($productGridImages);
});
}
function buildGrid() {
console.log("Galapagos.ProductList buildGrid()");
for (var i = maxGridUnit; i > 0; i--) {
orphanProductCount = productCount % i;
if(productCount <= maxGridUnit || i > 4) {
if(0 === orphanProductCount) {
$productGrid.addClass('item-grid-' + i);
isGridBuilt = true;
break;
}
} else {
if(0 === productCount % 9) { // if productCount is a multiple of 9, use the 9-grid. we use 9-grid only for multiples of 9 because 8-grid looks more interesting.
$productGrid.addClass('item-grid-' + 9);
} else { // otherwise, use the 8-grid and put the remainder into the orphan div
$productGrid.addClass('item-grid-' + maxGridUnit);
$orphanProducts = Y.all('.productlist-item').slice((productCount % maxGridUnit) * -1);
$productGridOrphans
.append($orphanProducts)
.addClass('item-grid-' + productCount % maxGridUnit);
}
isGridBuilt = true;
break;
}
}
if(isGridBuilt) {
$productGrid.removeClass('loading').removeClass('loading-height');
loadGridImages();
}
}
function setActiveCategory() {
console.log("Galapagos.ProductList setActiveCategory()");
var catNavItemCount = $catNav.all('.catnav-item').size();
for (var i = catNavItemCount - 1; i > 0; i--) {
var $item = $catNav.all('.catnav-item').item(i);
var $link = $item.one('.catnav-link');
var category = Y.QueryString.parse(location.search.substring(1)).category;
var href = Y.QueryString.parse($link.getAttribute('href').substring(2)).category;
if(category && href && category === href) {
$item.addClass('active-link');
}
else if(!category) {
$catNav.one('#jsCatNavRoot').addClass('active-link');
}
}
}
function loadGridImages() {
console.log("Galapagos.ProductList loadGridImages()");
$productGridImages.each(function(image) {
ImageLoader.load(image.removeAttribute('data-load'), { load: true });
image.on('load', function(){
if (image.hasClass('productlist-image--main.has-alt-image')) {
image.siblings('.productlist-image--alt').removeClass('galapagos-hidden');
}
});
});
}
function bindTweakListeners() {
console.log("Galapagos.ProductList bindTweakListeners()");
if (Y.Global) {
Y.Global.on(['tweak:beforeopen', 'tweak:close', 'tweak:reset'], function() {
setTimeout(function(){
Galapagos.ProductList.init();
}, 1000);
});
Y.Global.on(['tweak:beforeopen'], function() {
setTimeout(function(){
Galapagos.ProductList.init();
$productGrid.one('.productlist-item').addClass('is-hovered');
}, 1000);
});
Y.Global.on(['tweak:close'], function() {
setTimeout(function(){
Galapagos.ProductList.init();
$productGrid.one('.productlist-item').removeClass('is-hovered');
}, 1000);
});
}
Galapagos.TweakListener.listen('product-grid-style', function(value) {
if('Organic' === value) {
buildGrid();
} else {
$productGrid.append($orphanProducts);
loadGridImages();
}
});
Galapagos.TweakListener.listen('product-info-style', function(value) {
if('Overlay' === value) {
$productGrid.one('.productlist-item').addClass('is-hovered');
} else {
$productGrid.one('.productlist-item').removeClass('is-hovered');
}
});
Galapagos.TweakListener.listen('productImageAspectRatio', function(value) {
loadGridImages();
});
Galapagos.TweakListener.listen('productImageSpacing', function(value) {
loadGridImages();
});
}
return {
init:init
}
}());
Y.on('domready', function() {
Galapagos.Site.init();
});
});
My Attempts
My first few attempts have been dropping the jsProductItemImages div from the Product view and dumping in the entire figure block from the Quick View then updating the associated css. While it pulls in the images (I can see them in the inspector and they take up space on the page) it shows up as being blank.
I also tried only using the thumbnails section from the Quick View and limiting the Product View to only show the first image by using {.section items.0} but then any thumbnail I clicked wouldn't swap out without writing the script for it (obviously) but I didn't want to write something like that when I know it exists in the code already!
Any help would be greatly appreciated!
UPDATE:
After replacing the product view markup with the quick view markup I ran into these errors
Uncaught TypeError: Cannot read property 'all' of null site.js:104
init # site.js:104
init # site.js:17
(anonymous function) # site.js:432
_notify # common-2a739bf…-min.js:1479
notify # common-2a739bf…-min.js:1479
_notify # common-2a739bf…-min.js:1475
_procSubs # common-2a739bf…-min.js:1476
fireSimple # common-2a739bf…-min.js:1476
_fire # common-2a739bf…-min.js:1476
fire # common-2a739bf…-min.js:1489
_load # common-2a739bf…-min.js:1463
f # common-2a739bf…-min.js:1457
Unsure why it's hitting an error with .all because it should be addressing the same array of images in both situations?
There's a few questions buried in the this post but let me answer the Quick View question specifically since that's what you're looking to "fix".
Squarespace uses a modular system of JavaScript/CSS add-ons called "rollups". If you pull the source code you'll see a window object that contains the current configuration of any given page. When visiting a Products page, the system triggers the use of their quick view JS and accommodating CSS file. This is where you'll want to be looking. The JS you're digging into is not relevant to Quick View (I don't believe).
Quick View Rollup JS: http://static.squarespace.com/universal/scripts-compressed/product-quick-view-6a1e5642b473ebbb5944-min.js
Quick View Rollup CSS: http://static.squarespace.com/universal/styles-compressed/product-quick-view-eb4b900ac0155bed2f175aa82e2a7c17-min.css
These rollups are triggered off of JavaScript hooks in the template files. What you'll need to do is experiment with using the Galapagos product template word and word so it has the same classes and data-attributes, and see if that works. It would take far too long to cover all of the details of what you need to do without actually working on the project. I would start here first and see if you can setup your product template to triggers the Quick View JS as is, without customization.
I am developing a windows desktop application using node.js and backbone.js.
I want to perform an action when the user closes the app by clicking on the close button on the title bar or right clicking on the application from windows taskbar.
My app.js looks like this,
var app = module.exports = require('appjs');
app.serveFilesFrom(__dirname + '/content/assets/');
var menubar = app.createMenu([ {
label : '&File',
submenu : [ {
label : 'E&xit',
action : function() {
window.close();
}
},{
label : 'New',
action : function() {
window.test();
}
} ]
}, {
label : '&Window',
submenu : [ {
label : 'Fullscreen',
action : function(item) {
window.frame.fullscreen();
console.log(item.label + " called.");
}
}, {
label : 'Minimize',
action : function() {
console.log("df");
window.frame.minimize();
}
}, {
label : 'Maximize',
action : function() {
console.log("nnnnnnlaaaaaaaaaaaaaaa");
window.frame.maximize();
}
}, {
label : ''// separator
}, {
label : 'Restore',
action : function() {
window.frame.restore();
}
} ]
} ]);
menubar.on('select', function(item) {
console.log("menu item " + item.label + " clicked");
});
var trayMenu = app.createMenu([ {
label : 'Show',
action : function() {
window.frame.show();
},
}, {
label : 'Minimize',
action : function() {
window.frame.hide();
}
}, {
label : 'Exit',
action : function() {
window.close();
}
} ]);
var statusIcon = app.createStatusIcon({
icon : './data/content/icons/32.png',
tooltip : 'AppJS Hello World',
menu : trayMenu
});
var window = app.createWindow({
width : 1024,// 640
height : 768,
showChrome: true,
icons : __dirname + '/content/icons'
});
window.on('create', function() {
console.log("Window Created");
window.frame.show();
window.frame.center();
window.frame.maximize();
window.frame.setMenuBar(menubar);
});
window.on('ready', function() {
console.log("Window Ready");
window.require = require;
window.process = process;
window.module = module;
//window.frame.openDevTools();
window.fileAssoc = process.mainModule.filename;
//window.readMyFile();
function F12(e) {
return e.keyIdentifier === 'F12'
}
function Command_Option_J(e) {
return e.keyCode === 74 && e.metaKey && e.altKey
}
});*/
window.addEventListener('keydown', function(e) {
console.log("hi");
if (F12(e) || Command_Option_J(e)) {
window.frame.openDevTools();
}
});
});
Please find the attached screenshot. I am able to perform actions on custom added functionalities inside "File" & "Windows".
But i don't know how to capture the event when the default app close button in the title bar is clicked or closed by right clicking on the application from windows task bar. Please help.
Thanks in Advance
UPDATED (added a few more code lines to show proper way for event to fire)
You should do like this:
var gui = require("nw.gui");
var win_main = gui.Window.get();
win_main.on('close', function () {
this.hide(); // Pretend to be closed already
alert("Closing...");
// here you detect if data is saved, and if not, ask user if they want to save
this.close(true); // if this line executes the app closes, if not,
// app stays opened
});
I tried the above and it works perfect. It catches both "Close button" clicks and key shortcuts like "Ctrl+F4" in Windows.
For further reading (moved from comments):
http://tutorialzine.com/2015/01/your-first-node-webkit-app/
https://nodesource.com/blog/node-desktop-applications
https://gist.github.com/LeCoupa/80eca2716a2b13c37cce
https://github.com/nwjs/nw.js/
https://github.com/nwjs/nw.js/wiki/window
Finally I have done this by hiding the default title bar and adding a custom one.
For that, I set "showChrome" attribute in appJs to false during the window creation, which is by default true.
The code change is
var window = app.createWindow({
width : 1024,// 640
height : 768,
**showChrome: false**,
icons : __dirname + '/content/icons'
});
But when the 'showChrome' attribute is set to false task bar also will be hidden until we minimise or restore the app.
I am currently developing a calendar where activities can be drag&dropped to other days.
When an activity is dropped into a different day, I show a custom modal using durandal's dialog plugin. The problem is when an user closes the modal, the activity has to revert to its original position. When an activity is dropped the following code is called:
function showDroppedActivityModal(obj) {
require(['modals/droppedActivityModal/droppedActivityModal', 'moment'], function(droppedActivityModal, moment) {
droppedActivityModal.show(obj).then(function(response) {
if(response !== false) {
...
}
// dialog closes
else {
calendarView.revertActivity.notify({ revert: true})
}
});
});
}
In my calendarView I implemented the revertActivity event to set revert to true but the function never re-evaluates itself but i'm able to receive the new revert value (true).
$(activity).draggable({
revert: function() {
var revert = false;
self.revertActivity.attach(function(sender, args) {
revert = args.revert;
});
return revert;
}
});
Custom event code:
function CalendarEvent(sender) {
this._sender = sender;
this._listeners = [];
}
CalendarEvent.prototype = {
attach : function (listener) {
this._listeners.push(listener);
},
notify : function (args) {
var index;
for (index = 0; index < this._listeners.length; index += 1) {
this._listeners[index](this._sender, args);
}
},
remove : function (listener){
this._listeners.remove(listener);
}
};
this.revertActivity = new CalendarEvent(this);
Looking off this example, notice how clicking on the Search button brings up a modal form with a darkened overlay behind it. Now notice how clicking on the Column Chooser button brings up a modal form but no overlay behind it.
My question is: how do I get the dark overlay to appear behind my Column Chooser popup?
There are currently undocumented option of the columnChooser:
$(this).jqGrid('columnChooser', {modal: true});
The demo demonstrate this. One can set default parameters for the columnChooser with respect of $.jgrid.col too:
$.extend(true, $.jgrid.col, {
modal: true
});
Recently I posted the suggestion to extend a little functionality of the columnChooser, but only a part of the changes are current code of the jqGrid. Nevertheless in the new version will be possible to set much more jQuery UI Dialog options with respect of new dialog_opts option. For example the usage of the following will be possible
$(this).jqGrid('columnChooser', {
dialog_opts: {
modal: true,
minWidth: 470,
show: 'blind',
hide: 'explode'
}
});
To use full features which I suggested you can just overwrite the standard implementation of columnChooser. One can do this by including the following code
$.jgrid.extend({
columnChooser : function(opts) {
var self = this;
if($("#colchooser_"+$.jgrid.jqID(self[0].p.id)).length ) { return; }
var selector = $('<div id="colchooser_'+self[0].p.id+'" style="position:relative;overflow:hidden"><div><select multiple="multiple"></select></div></div>');
var select = $('select', selector);
function insert(perm,i,v) {
if(i>=0){
var a = perm.slice();
var b = a.splice(i,Math.max(perm.length-i,i));
if(i>perm.length) { i = perm.length; }
a[i] = v;
return a.concat(b);
}
}
opts = $.extend({
"width" : 420,
"height" : 240,
"classname" : null,
"done" : function(perm) { if (perm) { self.jqGrid("remapColumns", perm, true); } },
/* msel is either the name of a ui widget class that
extends a multiselect, or a function that supports
creating a multiselect object (with no argument,
or when passed an object), and destroying it (when
passed the string "destroy"). */
"msel" : "multiselect",
/* "msel_opts" : {}, */
/* dlog is either the name of a ui widget class that
behaves in a dialog-like way, or a function, that
supports creating a dialog (when passed dlog_opts)
or destroying a dialog (when passed the string
"destroy")
*/
"dlog" : "dialog",
/* dlog_opts is either an option object to be passed
to "dlog", or (more likely) a function that creates
the options object.
The default produces a suitable options object for
ui.dialog */
"dlog_opts" : function(opts) {
var buttons = {};
buttons[opts.bSubmit] = function() {
opts.apply_perm();
opts.cleanup(false);
};
buttons[opts.bCancel] = function() {
opts.cleanup(true);
};
return $.extend(true, {
"buttons": buttons,
"close": function() {
opts.cleanup(true);
},
"modal" : opts.modal ? opts.modal : false,
"resizable": opts.resizable ? opts.resizable : true,
"width": opts.width+20,
resize: function (e, ui) {
var $container = $(this).find('>div>div.ui-multiselect'),
containerWidth = $container.width(),
containerHeight = $container.height(),
$selectedContainer = $container.find('>div.selected'),
$availableContainer = $container.find('>div.available'),
$selectedActions = $selectedContainer.find('>div.actions'),
$availableActions = $availableContainer.find('>div.actions'),
$selectedList = $selectedContainer.find('>ul.connected-list'),
$availableList = $availableContainer.find('>ul.connected-list'),
dividerLocation = opts.msel_opts.dividerLocation || $.ui.multiselect.defaults.dividerLocation;
$container.width(containerWidth); // to fix width like 398.96px
$availableContainer.width(Math.floor(containerWidth*(1-dividerLocation)));
$selectedContainer.width(containerWidth - $availableContainer.outerWidth() - ($.browser.webkit ? 1: 0));
$availableContainer.height(containerHeight);
$selectedContainer.height(containerHeight);
$selectedList.height(Math.max(containerHeight-$selectedActions.outerHeight()-1,1));
$availableList.height(Math.max(containerHeight-$availableActions.outerHeight()-1,1));
}
}, opts.dialog_opts || {});
},
/* Function to get the permutation array, and pass it to the
"done" function */
"apply_perm" : function() {
$('option',select).each(function(i) {
if (this.selected) {
self.jqGrid("showCol", colModel[this.value].name);
} else {
self.jqGrid("hideCol", colModel[this.value].name);
}
});
var perm = [];
//fixedCols.slice(0);
$('option:selected',select).each(function() { perm.push(parseInt(this.value,10)); });
$.each(perm, function() { delete colMap[colModel[parseInt(this,10)].name]; });
$.each(colMap, function() {
var ti = parseInt(this,10);
perm = insert(perm,ti,ti);
});
if (opts.done) {
opts.done.call(self, perm);
}
},
/* Function to cleanup the dialog, and select. Also calls the
done function with no permutation (to indicate that the
columnChooser was aborted */
"cleanup" : function(calldone) {
call(opts.dlog, selector, 'destroy');
call(opts.msel, select, 'destroy');
selector.remove();
if (calldone && opts.done) {
opts.done.call(self);
}
},
"msel_opts" : {}
}, $.jgrid.col, opts || {});
if($.ui) {
if ($.ui.multiselect ) {
if(opts.msel == "multiselect") {
if(!$.jgrid._multiselect) {
// should be in language file
alert("Multiselect plugin loaded after jqGrid. Please load the plugin before the jqGrid!");
return;
}
opts.msel_opts = $.extend($.ui.multiselect.defaults,opts.msel_opts);
}
}
}
if (opts.caption) {
selector.attr("title", opts.caption);
}
if (opts.classname) {
selector.addClass(opts.classname);
select.addClass(opts.classname);
}
if (opts.width) {
$(">div",selector).css({"width": opts.width,"margin":"0 auto"});
select.css("width", opts.width);
}
if (opts.height) {
$(">div",selector).css("height", opts.height);
select.css("height", opts.height - 10);
}
var colModel = self.jqGrid("getGridParam", "colModel");
var colNames = self.jqGrid("getGridParam", "colNames");
var colMap = {}, fixedCols = [];
select.empty();
$.each(colModel, function(i) {
colMap[this.name] = i;
if (this.hidedlg) {
if (!this.hidden) {
fixedCols.push(i);
}
return;
}
select.append("<option value='"+i+"' "+
(this.hidden?"":"selected='selected'")+">"+colNames[i]+"</option>");
});
function call(fn, obj) {
if (!fn) { return; }
if (typeof fn == 'string') {
if ($.fn[fn]) {
$.fn[fn].apply(obj, $.makeArray(arguments).slice(2));
}
} else if ($.isFunction(fn)) {
fn.apply(obj, $.makeArray(arguments).slice(2));
}
}
var dopts = $.isFunction(opts.dlog_opts) ? opts.dlog_opts.call(self, opts) : opts.dlog_opts;
call(opts.dlog, selector, dopts);
var mopts = $.isFunction(opts.msel_opts) ? opts.msel_opts.call(self, opts) : opts.msel_opts;
call(opts.msel, select, mopts);
// fix height of elements of the multiselect widget
var resizeSel = "#colchooser_"+$.jgrid.jqID(self[0].p.id),
$container = $(resizeSel + '>div>div.ui-multiselect'),
$selectedContainer = $(resizeSel + '>div>div.ui-multiselect>div.selected'),
$availableContainer = $(resizeSel + '>div>div.ui-multiselect>div.available'),
containerHeight,
$selectedActions = $selectedContainer.find('>div.actions'),
$availableActions = $availableContainer.find('>div.actions'),
$selectedList = $selectedContainer.find('>ul.connected-list'),
$availableList = $availableContainer.find('>ul.connected-list');
$container.height($container.parent().height()); // increase the container height
containerHeight = $container.height();
$selectedContainer.height(containerHeight);
$availableContainer.height(containerHeight);
$selectedList.height(Math.max(containerHeight-$selectedActions.outerHeight()-1,1));
$availableList.height(Math.max(containerHeight-$availableActions.outerHeight()-1,1));
// extend the list of components which will be also-resized
selector.data('dialog').uiDialog.resizable("option", "alsoResize",
resizeSel + ',' + resizeSel +'>div' + ',' + resizeSel + '>div>div.ui-multiselect');
}
});
In the case you can continue to use the original minimized version of jquery.jqGrid.min.js and the code which use can be just $(this).jqGrid('columnChooser');. Together with all default settings it will be like
$.extend(true, $.ui.multiselect, {
locale: {
addAll: 'Make all visible',
removeAll: 'Hidde All',
itemsCount: 'Avlialble Columns'
}
});
$.extend(true, $.jgrid.col, {
width: 450,
modal: true,
msel_opts: {dividerLocation: 0.5},
dialog_opts: {
minWidth: 470,
show: 'blind',
hide: 'explode'
}
});
$grid.jqGrid('navButtonAdd', '#pager', {
caption: "",
buttonicon: "ui-icon-calculator",
title: "Choose columns",
onClickButton: function () {
$(this).jqGrid('columnChooser');
}
});
The demo demonstrate the approach. The main advantage of the changes - the really resizable Column Chooser:
UPDATED: Free jqGrid fork of jqGrid, which I develop starting with the end of 2014, contains of cause the modified code of columnChooser.
I get the following error while trying the code on the mobile device.
Result of expression 'selector.data('dialog').uiDialog' [undefined] is not an object.
The error points to the following line of code.
selector.data('dialog').uiDialog.resizable("option", "alsoResize", resizeSel + ',' + resizeSel +'>div' + ',' + resizeSel + '>div>div.ui-multiselect');
When I inspect the code, I find that the data object does not have anything called uiDialog.
just been looking thru the code, try adding this line:
jqModal : true,
to this code:
$grid.jqGrid('navButtonAdd', '#pager', {
caption: "",
buttonicon: "ui-icon-calculator",
title: "Choose columns",
onClickButton: function () {
....
like this:
$grid.jqGrid('navButtonAdd', '#pager', {
caption: "",
buttonicon: "ui-icon-calculator",
title: "Choose columns",
jqModal : true,
onClickButton: function () {
....
I'm trying to build a Javascript listener for a small page that uses AJAX to load content based on the anchor in the URL. Looking online, I found and modified a script that uses setInterval() to do this and so far it works fine. However, I have other jQuery elements in the $(document).ready() for special effects for the menus and content. If I use setInterval() no other jQuery effects work. I finagled a way to get it work by including the jQuery effects in the loop for setInterval() like so:
$(document).ready(function() {
var pageScripts = function() {
pageEffects();
pageURL();
}
window.setInterval(pageScripts, 500);
});
var currentAnchor = null;
function pageEffects() {
// Popup Menus
$(".bannerMenu").hover(function() {
$(this).find("ul.bannerSubmenu").slideDown(300).show;
}, function() {
$(this).find("ul.bannerSubmenu").slideUp(400);
});
$(".panel").hover(function() {
$(this).find(".panelContent").fadeIn(200);
}, function() {
$(this).find(".panelContent").fadeOut(300);
});
// REL Links Control
$("a[rel='_blank']").click(function() {
this.target = "_blank";
});
$("a[rel='share']").click(function(event) {
var share_url = $(this).attr("href");
window.open(share_url, "Share", "width=768, height=450");
event.preventDefault();
});
}
function pageURL() {
if (currentAnchor != document.location.hash) {
currentAnchor = document.location.hash;
if (!currentAnchor) {
query = "section=home";
} else {
var splits = currentAnchor.substring(1).split("&");
var section = splits[0];
delete splits[0];
var params = splits.join("&");
var query = "section=" + section + params;
}
$.get("loader.php", query, function(data) {
$("#load").fadeIn("fast");
$("#content").fadeOut(100).html(data).fadeIn(500);
$("#load").fadeOut("fast");
});
}
}
This works fine for a while but after a few minutes of the page being loaded, it drags to a near stop in IE and Firefox. I checked the FF Error Console and it comes back with an error "Too many Recursions." Chrome seems to not care and the page continues to run more or less normally despite the amount of time it's been open.
It would seem to me that the pageEffects() call is causing the issue with the recursion, however, any attempts to move it out of the loop breaks them and they cease to work as soon as setInterval makes it first loop.
Any help on this would be greatly appreciated!
I am guessing that the pageEffects need added to the pageURL content.
At the very least this should be more efficient and prevent duplicate handlers
$(document).ready(function() {
pageEffects($('body'));
(function(){
pageURL();
window.setTimeout(arguments.callee, 500);
})();
});
var currentAnchor = null;
function pageEffects(parent) {
// Popup Menus
parent.find(".bannerMenu").each(function() {
$(this).unbind('mouseenter mouseleave');
var proxy = {
subMenu: $(this).find("ul.bannerSubmenu"),
handlerIn: function() {
this.subMenu.slideDown(300).show();
},
handlerOut: function() {
this.subMenu.slideUp(400).hide();
}
};
$(this).hover(proxy.handlerIn, proxy.handlerOut);
});
parent.find(".panel").each(function() {
$(this).unbind('mouseenter mouseleave');
var proxy = {
content: panel.find(".panelContent"),
handlerIn: function() {
this.content.fadeIn(200).show();
},
handlerOut: function() {
this.content.slideUp(400).hide();
}
};
$(this).hover(proxy.handlerIn, proxy.handlerOut);
});
// REL Links Control
parent.find("a[rel='_blank']").each(function() {
$(this).target = "_blank";
});
parent.find("a[rel='share']").click(function(event) {
var share_url = $(this).attr("href");
window.open(share_url, "Share", "width=768, height=450");
event.preventDefault();
});
}
function pageURL() {
if (currentAnchor != document.location.hash) {
currentAnchor = document.location.hash;
if (!currentAnchor) {
query = "section=home";
} else {
var splits = currentAnchor.substring(1).split("&");
var section = splits[0];
delete splits[0];
var params = splits.join("&");
var query = "section=" + section + params;
}
var content = $("#content");
$.get("loader.php", query, function(data) {
$("#load").fadeIn("fast");
content.fadeOut(100).html(data).fadeIn(500);
$("#load").fadeOut("fast");
});
pageEffects(content);
}
}
Thanks for the suggestions. I tried a few of them and they still did not lead to the desirable effects. After some cautious testing, I found out what was happening. With jQuery (and presumably Javascript as a whole), whenever an AJAX callback is made, the elements brought in through the callback are not binded to what was originally binded in the document, they must be rebinded. You can either do this by recalling all the jQuery events on a successful callback or by using the .live() event in jQuery's library. I opted for .live() and it works like a charm now and no more recursive errors :D.
$(document).ready(function() {
// Popup Menus
$(".bannerMenu").live("hover", function(event) {
if (event.type == "mouseover") {
$(this).find("ul.bannerSubmenu").slideDown(300);
} else {
$(this).find("ul.bannerSubmenu").slideUp(400);
}
});
// Rollover Content
$(".panel").live("hover", function(event) {
if (event.type == "mouseover") {
$(this).find(".panelContent").fadeIn(200);
} else {
$(this).find(".panelContent").fadeOut(300);
}
});
// HREF Events
$("a[rel='_blank']").live("click", function(event) {
var target = $(this).attr("href");
window.open(target, "_blank");
event.preventDefault();
});
$("a[rel='share']").live("click", function(event) {
var share_url = $(this).attr("href");
window.open(share_url, "Share", "width=768, height=450");
event.preventDefault();
});
setInterval("checkAnchor()", 500);
});
var currentAnchor = null;
function checkAnchor() {
if (currentAnchor != document.location.hash) {
currentAnchor = document.location.hash;
if (!currentAnchor) {
query = "section=home";
} else {
var splits = currentAnchor.substring(1).split("&");
var section = splits[0];
delete splits[0];
var params = splits.join("&");
var query = "section=" + section + params;
}
$.get("loader.php", query, function(data) {
$("#load").fadeIn(200);
$("#content").fadeOut(200).html(data).fadeIn(200);
$("#load").fadeOut(200);
});
}
}
Anywho, the page works as intended even in IE (which I rarely check for compatibility). Hopefully, some other newb will learn from my mistakes :p.