I have some html to popup a jQuery UI modal:
Click me
With this javascript code:
$('.popup').click(function() {
var a = this;
var dialog = $('<div>').load($(a).attr('href'), function() {
var form = dialog.find('form');
dialog.dialog({
modal: true,
title: $(a).attr('title'),
buttons: {
Add : function () {
$.post($(form).attr('action'), $(form).serialize());
dialog.dialog('close');
},
Cancel: function () {
dialog.dialog('close');
}
}
});
if ($(a).attr('data-third')) {
// Add here a button
}
});
return false;
});
The idea is the resource in the modal contains a form, on submit of the modal the form is submitted. Now when Click me exists, it means a third button is placed in the modal in this order: Cancel | Add | Add & new.
I tried this code:
if($(a).attr('data-third')) {
var buttons = dialog.dialog('option', 'buttons');
buttons[$(a).attr('data-third')] = function(){
// Callback here
}
dialog.dialog('option', 'buttons', buttons);
}
I got a new button, but the order is Add & new | Cancel | Add. How can I make sure the order is Cancel | Add | Add & new?
With this code you should always get Cancel | Add | Add & new? (from left to right.
$('.popup').click(function() {
var a = this;
var dialog = $('<div>').load($(a).attr('href'), function() {
var form = dialog.find('form');
dialog.dialog({
modal: true,
title: $(a).attr('title'),
buttons: {
Cancel: function() {
dialog.dialog('close');
},
Add: function() {
$.post($(form).attr('action'), $(form).serialize());
dialog.dialog('close');
}
}
});
if ($(a).data('third')) {
var buttons = dialog.dialog('option', 'buttons');
console.log(buttons);
buttons[$(a).data('third')] = function() {
// Callback here
}
dialog.dialog('option', 'buttons', buttons);
}
});
return false;
});
Fiddle here: http://jsfiddle.net/rqq2p/1/
Remember to use data() and not attrib() to get the attribute from the link!
EDIT - basically "buttons" is an object, and each property is one of the buttons. I think that the order in which the buttons appear is the order in which the properties are added. In my example Cancel is the leftmost button because it was the first added property and add & new it's the rightmost because it was the latest. If you need to fiddle with the order you could create a new object and add the properties in the order you want:
if ($(a).data('third')) {
var buttons = dialog.dialog('option', 'buttons');
var newButtons = {};
newButtons[$(a).data('third')] = function() {
// Callback here
}
newButtons['Cancel'] = buttons['Cancel'];
newButtons['Add'] = buttons['Add'];
dialog.dialog('option', 'buttons', newButtons);//now add & new should be the leftmost
}
Untested, but I would imagine the array key should be buttons.length like this
if($(a).attr('data-third')) {
var buttons = dialog.dialog('option', 'buttons');
buttons[buttons.length] = function(){
// Callback here
}
dialog.dialog('option', 'buttons', buttons);
}
Related
I'm creating a javascript function which creates a modal. Here's the function:
function createModal(options) {
var self = this;
modalHeaderText = options.header;
modalBodyText = options.body;
$modal = $('<div />').addClass('modal').appendTo('body');
$modalOverlay = $('<div />').addClass('modal-overlay').appendTo($modal);
$modalContainer = $('<div />').addClass('modal-container').appendTo($modal);
$modalHeader = $('<div />').addClass('modal-header').addClass(options.headerClass).html(modalHeaderText).appendTo($modalContainer);
$modalBody = $('<div />').addClass('modal-body').addClass(options.bodyClass).html(modalBodyText).appendTo($modalContainer);
if (options.buttons) {
$modalFooter = $('<div />').addClass('modal-footer').appendTo($modalContainer);
$.each(options.buttons, function(name, buttonOptions) {
$modalButton = $('<button />').addClass(buttonOptions.class).html(name).appendTo($modalFooter);
if(buttonOptions.callback) {
$modalButton.on('click', function() {
buttonOptions.callback();
});
} else {
$modalButton.on('click', function(e) {
$modal.remove();
});
};
});
};
$modal.addClass('active');
if (options.closeOnOverlayClick == true) {
$modalOverlay.on('click', function(e) {
$modal.remove();
});
};
};
This works fine, but I want to be able to call the function within the same function, like this:
$('#modal').on('click', function(e){
e.preventDefault();
createModal({
header : 'Enter your name',
body : '<input type="text" class="name" />',
buttons : {
'OK' : {
class : 'btn btn-success',
callback : function() {
var name = self.$modalBody.find('.name').val();
if (!name) {
createModal({
header : 'Error',
body : 'You must provide a name',
buttons : {
'OK' : {
class : 'btn'
}
}
});
} else {
alert(name);
};
},
},
'Close' : {
class : 'btn btn-error'
}
}
});
});
What I want is the following: when someone clicks the button with ID "modal" (hence "#modal"), a modal is opened with a input. When the OK-button is pressed, it checks if the input ('name') has a value. If so, the value is shown in an alert. If not, a new modal is openend (over the current modal) with the text 'You must provide a name'.
If I enter a name, it works. The name is shown in an alert, and also the close button works. But if I do not enter a name, and the second modal is shown, all the variables in the function are overwritten.
How can I preserve the variables/elements from the first modal so that, after the second modal is shown (and cleared), the buttons from the first modal still work.
I've created a JSFiddle here: https://jsfiddle.net/6pq7ce0a/2/
You can test it like this:
1) click on 'open modal'
2) enter a name
3) click on 'ok'
4) the name is shown in an alert
==> this works
The problem is here:
1) click on 'open modal'
2) do NOT enter a name
3) click on 'ok'
4) a new modal is shown
5) click on 'ok' in the new (error) modal
6) the buttons from the first modal (with the input field) don't work anymore
Thanks in advance!
Update
If I change the function to the function below, the first modal does not work at all.
function createModal(options) {
var self = this;
var modalHeaderText = options.header;
var modalBodyText = options.body;
var $modal = $('<div />').addClass('modal').appendTo('body');
var $modalOverlay = $('<div />').addClass('modal-overlay').appendTo($modal);
var $modalContainer = $('<div />').addClass('modal-container').appendTo($modal);
var $modalHeader = $('<div />').addClass('modal-header').addClass(options.headerClass).html(modalHeaderText).appendTo($modalContainer);
var $modalBody = $('<div />').addClass('modal-body').addClass(options.bodyClass).html(modalBodyText).appendTo($modalContainer);
if (options.buttons) {
var $modalFooter = $('<div />').addClass('modal-footer').appendTo($modalContainer);
$.each(options.buttons, function(name, buttonOptions) {
var $modalButton = $('<button />').addClass(buttonOptions.class).html(name).appendTo($modalFooter);
if(buttonOptions.callback) {
$modalButton.on('click', function() {
buttonOptions.callback();
});
} else {
$modalButton.on('click', function(e) {
$modal.remove();
});
};
});
};
$modal.addClass('active');
if (options.closeOnOverlayClick == true) {
$modalOverlay.on('click', function(e) {
$modal.remove();
});
};
};
The problem is here:
var name = self.$modalBody.find('.name').val();
$modalBody is not defined if I add 'var' to all the elements.
So in addition to the comments above regarding not declaring var you also are storing a reference to window in the self variable. To avoid all of that I went down the road in this fiddle: https://jsfiddle.net/10fanzw6/1/.
Quick explanation.
First don't assign this to self as this is window
Second assign everything to the empty self object as well as a local var (for better readability)
Third pass the self var back to any button callback giving you access to any part of the modal you may need.
For posterity, including the updated function here:
function createModal(options) {
var self = {};
var modalHeaderText = options.header;
var modalBodyText = options.body;
var $modal = self.$modal = $('<div />').addClass('modal').appendTo('body');
var $modalOverlay = self.$modalOverlay = $('<div />').addClass('modal-overlay').appendTo($modal);
var $modalContainer = self.$modalContainer = $('<div />').addClass('modal-container').appendTo(self.$modal);
self.$modalHeader = $('<div />').addClass('modal-header').addClass(options.headerClass).html(modalHeaderText).appendTo($modalContainer);
self.$modalBody = $('<div />').addClass('modal-body').addClass(options.bodyClass).html(modalBodyText).appendTo($modalContainer);
if (options.buttons) {
var $modalFooter = self.$modalFooter = $('<div />').addClass('modal-footer').appendTo($modalContainer);
$.each(options.buttons, function(name, buttonOptions) {
var $modalButton = $('<button />').addClass(buttonOptions.class).html(name).appendTo($modalFooter);
if (buttonOptions.callback) {
$modalButton.on('click', function() {
buttonOptions.callback(self);
});
} else {
$modalButton.on('click', function(e) {
$modal.remove();
});
};
});
};
$modal.addClass('active');
if (options.closeOnOverlayClick == true) {
$modalOverlay.on('click', function(e) {
$modal.remove();
});
};
};
$('#modal').on('click', function(e) {
e.preventDefault();
createModal({
header: 'Enter your name',
body: '<input type="text" class="name" />',
buttons: {
'OK': {
class: 'btn btn-success',
callback: function(modal) {
var name = modal.$modalBody.find('.name').val();
if (!name) {
createModal({
header: 'Error',
body: 'You must provide a name',
buttons: {
'OK': {
class: 'btn'
}
}
});
} else {
alert(name);
};
},
},
'Close': {
class: 'btn btn-error'
}
}
});
});
Simply not using var in front of $modal variable causing it to be stored in window scope. When the next next $modal is closed, the variable is referencing to an already removed element, so nothing happens on first modal's Close button click.
The idea behind this to animate section with mousewheel - keyboard and swipe on enter and on exit. Each section has different animation.
Everything is wrapp inside a global variable. Here is a bigger sample
var siteGlobal = (function(){
init();
var init = function(){
bindEvents();
}
// then i got my function to bind events
var bindEvents = function(){
$(document).on('mousewheel', mouseNav());
$(document).on('keyup', mouseNav());
}
// then i got my function here for capture the event
var mouseNav = function(){
// the code here for capturing direction or keyboard
// and then check next section
}
var nextSection = function(){
// Here we check if there is prev() or next() section
// if there is do the change on the section
}
var switchSection = function(nextsection){
// Get the current section and remove active class
// get the next section - add active class
// get the name of the function with data-name attribute
// trow the animation
var funcEnter = window['section'+ Name + 'Enter'];
}
// Let's pretend section is call Intro
var sectionIntroEnter = function(){
// animation code here
}
var sectionIntroExit = function(){
// animation code here
}
}();
So far so good until calling funcEnter() and nothing happen
I still stuck to call those function...and sorry guys i'm really not a javascript programmer , i'm on learning process and this way it make it easy for me to read so i would love continue using this way of "coding"...Do someone has a clue ? Thanks
Your concatenation is right but it'd be better if you didn't create global functions to do this. Instead, place them inside of your own object and access the functions through there.
var sectionFuncs = {
A: {
enter: function() {
console.log('Entering A');
},
exit: function() {
console.log('Exiting A');
}
},
B: {
enter: function() {
console.log('Entering B');
},
exit: function() {
console.log('Exiting B');
}
}
};
function onClick() {
var section = this.getAttribute('data-section');
var functions = sectionFuncs[section];
functions.enter();
console.log('In between...');
functions.exit();
}
var buttons = document.querySelectorAll('button');
for (var i = 0; i < buttons.length; i++) {
buttons[i].addEventListener('click', onClick);
}
<button data-section="A">A</button>
<button data-section="B">B</button>
You could have an object that holds these functions, keyed by the name:
var enterExitFns = {
intro: {
enter: function () {
// animation code for intro enter
},
exit: function () {
// animation code for intro exit
}
},
details: {
enter: function () {
// animation code for details enter
},
exit: function () {
// animation code for details exit
}
}
};
var name = activeSection.attr('data-name');
enterExitFns[name].enter();
I am landing on the first page of DataTable, make some changes.
Then I move to the second page.
Actually, confirmation popup is shown but it navigate to the second page.
Expected: confirm pop is shown but it still landing on the first.
Here is my code:
$('#dataTable-id').on( 'page.dt', function (event) {
if( change ){
bootbox.dialog({
title: "Confirmation",
message : "Discard changes?",
buttons :{
main: {
label : "Leave",
className : "btn-primary",
callback: function (){
// To avoid broking page/length controller
// move to other pages
return true; // cancel draw
}
},
cancel: {
label : "Stay",
className : "btn-default",
callback : function() {
// stay at current page.
return true;
}
}
},onEscape: function () {return true;}
});
}
});
How to show confirmation popup before page change?
The page.dt event is only informational, it can not be canceled.
You can workaround that restriction by writing a custom preDrawCallback like discussed here: https://datatables.net/forums/discussion/25507
EDIT: You have to cancel the redraw generally and do the paging manually in the bootbox callback (as it does not work as a real modal dialog like the native javascript confirm()). I modified the above example to incorporate a bootbox confirm dialog on paging: https://jsfiddle.net/bk4nvds5/
$(document).ready(function () {
var pageLen = 10;
var originalPage = 0;
var originalPageLen = pageLen;
var gotoPage = 0;
var gotoPageLen = originalPageLen;
var fromBootbox = false;
var table = $('#example').DataTable({
"pageLength": pageLen,
"preDrawCallback": function (settings) {
if(table){ // ignore first draw
if (!fromBootbox) {
// To avoid broking page/length controller, we have to reset the paging information
gotoPage = settings._iDisplayStart;
gotoPageLen = settings._iDisplayLength;
bootbox.confirm("Are you sure?", function(result) {
console.log("goto page" + gotoPage + " (result: " + result + ")");
if (result) {
fromBootbox = true;
table.page.len(gotoPageLen);
table.page(gotoPage / gotoPageLen).draw(false);
fromBootbox = false;
}
});
settings._iDisplayStart = originalPage;
settings._iDisplayLength = originalPageLen;
$('[name="example_length"]').val(originalPageLen);
return false; // cancel draw
}else{
originalPage = settings._iDisplayStart;
originalPageLen = settings._iDisplayLength;
}
}
}
});
});
I have the following script that's used to create a modal with buttons. I just have the first lines below:
$.modal = function(options)
{
var settings = $.extend({}, $.modal.defaults, options),
root = getModalDiv(),
// Vars for resizeFunc and moveFunc
winX = 0,
winY = 0,
contentWidth = 0,
contentHeight = 0,
mouseX = 0,
mouseY = 0,
resized, content = '', contentObj;
and then later:
var buttonsFooter = false;
$.each(settings.buttons, function(key, value)
{
// Button zone
if (!buttonsFooter)
{
buttonsFooter = $('<div class="block-footer align-'+settings.buttonsAlign+'"></div>').insertAfter(contentBlockWrapper);
}
else
{
// Spacing
buttonsFooter.append(' ');
}
Here's the way I create the modal:
$.modal({
title: title,
closeButton: true,
content: content,
complete: function () {
applyTemplateSetup();
$($('#main-form')).updateTabs();
},
width: 900,
resizeOnLoad: true,
buttons: {
'Submit' : function () {
formSubmitHandler($('#main-form'));
}
}
});
What I would like to do is to use this script to create my modal with buttons and dynamically assign the button name. However when I tried replacing 'Submit' with the name of a javascript variable it didn't work. Does anyone have any idea how I could do what I need?
The reason replacing 'Submit' with a variable name doesn't work is because the quotes aren't needed when defining an object in that manner (unless the name contains some special characters). var obj = { 'Submit': function() { } } is exactly the same thing as var obj = { Submit: function() { } }.
You'll notice that you're already omitting the quotes like this in the object you're passing to $.modal().
However, while you can't write
var myVariable = 'abc';
var obj = { myVariable: 3 };
... and expect obj.abc to exist (obj.myVariable will exist), you can write
var myVariable = 'abc';
var obj[myVariable] = 3;
... and obj.abc will exist.
Thus, you could solve your problem in the following manner:
var buttons = { };
buttons[variableName] = function () {
formSubmitHandler($('#main-form'));
};
$.modal({
...
buttons: buttons
});
Edit: David Hedlund beat me to answering, and offered a similar answer which involves a little less code modification. He suggests only breaking out the buttons options instead of the entire options var. End Edit
First, break the options out into a variable:
var modalOpts = {
title: title,
closeButton: true,
content: content,
complete: function () {
applyTemplateSetup();
$($('#main-form')).updateTabs();
},
width: 900,
resizeOnLoad: true,
buttons: {
'Submit' : function () {
formSubmitHandler($('#main-form'));
}
}
};
$.modal(modalOpts);
Then break the button in question out of the options, and use square bracket notation to access and write the property:
var dynamicButtonName = 'Submit'; //Or whatever
var modalOpts = {
title: title,
closeButton: true,
content: content,
complete: function () {
applyTemplateSetup();
$($('#main-form')).updateTabs();
},
width: 900,
resizeOnLoad: true,
buttons: {}
};
modalOpts.buttons[dynamicButtonName] = function () {
formSubmitHandler($('#main-form'));
};
$.modal(modalOpts);
How do I dynamically update the items in a drop down?
I have a custom plugin for CKEditor that populates a drop down menu with a list of items which I can inject into my textarea.
This list of items comes from a Javascript array called maptags, which is updated dynamically for each page.
var maptags = []
This list of tags gets added to the drop down when you first click on it by the init: function. My problem is what if the items in that array change as the client changes things on the page, how can I reload that list to the updated array?
Here is my CKEditor Plugin code:
CKEDITOR.plugins.add('mapitems', {
requires: ['richcombo'], //, 'styles' ],
init: function (editor) {
var config = editor.config,
lang = editor.lang.format;
editor.ui.addRichCombo('mapitems',
{
label: "Map Items",
title: "Map Items",
voiceLabel: "Map Items",
className: 'cke_format',
multiSelect: false,
panel:
{
css: [config.contentsCss, CKEDITOR.getUrl(editor.skinPath + 'editor.css')],
voiceLabel: lang.panelVoiceLabel
},
init: function () {
this.startGroup("Map Items");
//this.add('value', 'drop_text', 'drop_label');
for (var this_tag in maptags) {
this.add(maptags[this_tag][0], maptags[this_tag][1], maptags[this_tag][2]);
}
},
onClick: function (value) {
editor.focus();
editor.fire('saveSnapshot');
editor.insertHtml(value);
editor.fire('saveSnapshot');
}
});
}
});
I think I just solved this actually.
Change your init like this:
init: function () {
var rebuildList = CKEDITOR.tools.bind(buildList, this);
rebuildList();
$(editor).bind('rebuildList', rebuildList);
},
And define the buildList function outside that scope.
var buildListHasRunOnce = 0;
var buildList = function () {
if (buildListHasRunOnce) {
// Remove the old unordered list from the dom.
// This is just to cleanup the old list within the iframe
$(this._.panel._.iframe.$).contents().find("ul").remove();
// reset list
this._.items = {};
this._.list._.items = {};
}
for (var i in yourListOfItems) {
var item = yourListOfItems[i];
// do your add calls
this.add(item.id, 'something here as html', item.text);
}
if (buildListHasRunOnce) {
// Force CKEditor to commit the html it generates through this.add
this._.committed = 0; // We have to set to false in order to trigger a complete commit()
this.commit();
}
buildListHasRunOnce = 1;
};
The clever thing about the CKEDITOR.tools.bind function is that we supply "this" when we bind it, so whenever the rebuildList is triggered, this refer to the richcombo object itself which I was not able to get any other way.
Hope this helps, it works fine for me!
ElChe
I could not find any helpful documenatation around richcombo, i took a look to the source code and got an idea of the events i needed.
#El Che solution helped me to get through this issue but i had another approach to the problem because i had a more complex combobox structure (search,groups)
var _this = this;
populateCombo.call(_this, data);
function populateCombo(data) {
/* I have a search workaround added here */
this.startGroup('Default'); /* create default group */
/* add items with your logic */
for (var i = 0; i < data.length; i++) {
var dataitem = data[i];
this.add(dataitem.name, dataitem.description, dataitem.name);
}
/* other groups .... */
}
var buildListHasRunOnce = 0;
/* triggered when combo is shown */
editor.on("panelShow", function(){
if (buildListHasRunOnce) {
// reset list
populateCombo.call(_this, data);
}
buildListHasRunOnce = 1;
});
/* triggered when combo is hidden */
editor.on("panelHide", function(){
$(_this._.list.element.$).empty();
_this._.items = {};
_this._.list._.items = {};
});
NOTE
All above code is inside addRichCombo init callback
I remove combobox content on "panelHide" event
I repopulate combobox on "panelShow" event
Hope this helps