Same function in function, preserve variables/elements - javascript

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.

Related

How to change the text with JS

I am trying to modify this code, so after I create the column, and let's say I want to change the title of it, so I have the edit button, once I click that one, I want to be able to type and change the title of the column.
For the whole code click here.
function Column(name) {
if (name.length > 0) {
var self = this; // useful for nested functions
this.id = randomString();
this.name = name;
this.$element = createColumn();
function createColumn() {
var $column = $("<div>").addClass("column");
var $columnTitle = $("<h3>")
.addClass("column-title")
.text(self.name);
var $columnTitleEdit = $("<button>")
.addClass("btn-edit")
.text("Edit");
var $columnCardList = $("<ul>").addClass("column-card-list");
var $columnDelete = $("<button>")
.addClass("btn-delete")
.text("x");
var $columnAddCard = $("<button>")
.addClass("add-card")
.text("Add a card");
$columnDelete.click(function() {
self.removeColumn();
});
$columnAddCard.click(function(event) {
self.addCard(new Card(prompt("Enter the name of the card")));
});
$columnTitleEdit.click(function(event) { //How to edit this code here so i can rename the title of the Column?
self.editTitle();
});
$column
.append($columnTitle)
.append($columnDelete)
.append($columnAddCard)
.append($columnCardList)
.append($columnTitleEdit);
return $column;
}
} else if (name.length == 0) {
alert("please type something");
$(".create-column").click();
} else {
return;
}
}
Column.prototype = {
addCard: function(card) {
this.$element.children("ul").append(card.$element);
},
removeColumn: function() {
this.$element.remove();
},
editTitle: function() {
if (this.$element == "true") {
this.$element.contentEditable = "false"; //How to edit this code here so i can rename the title of the Column?
} else {
this.$element == "true";
}
}
};
All you have to do is to add an event listener to the edit button. The handler should either replace the title with a textarea, or add the contenteditable attribute to the title element. Here's an example:
// ...
var $columnTitleEdit = $("<button>")
.addClass("btn-edit")
.text("Edit")
.on("click", function(){ //The event listener
if ($(this).hasClass("btn-save")){ //If we're currently editing the title
$columnTitle.attr("contenteditable", false);
$(this).text("Edit").removeClass("btn-save");
} else { //If we're not editing the title
$columnTitle.attr("contenteditable", true).focus();
$(this).text("Save").addClass("btn-save");
}
});

How to detect a click event from anywhere in html including iFrame?

In my sample work, I would like to detect the click event from anywhere of my html element. It works when I click on direct html but not for iFrames - so I would like to hear from any where of my window. What is the correct way to do that using jQuery ?
Even I would like to hear from dynamic htmls too. (my case there is a loop with numbers
here is the demo
my code :
var findAllClicks = function() {
$(document).on('click', '*', function() {
console.log($(this)[0]); //only works on direct html element.
})
}
var outsideFunction = function() {
console.log('loop ends');
}
outsideFunction();
$(function() {
//finding click events from any where.
findAllClicks();
var total = 1000;
var i = 0;
var iterate = function() {
setTimeout(function() {
var place = $('#iFrame1').contents().find('#iFrame2').contents().find('body');
place.append('<ul class="list"></ul>');
for (i = 0; i < total; i++) {
place.find('.list').append('<li>' + i + '</li>');
}
}, 3000);
//how to find all this done from outside of this function?
}
var iFrame1 = $('<iframe />', {
id: 'iFrame1'
});
var iFrame2 = $('<iframe />', {
id: 'iFrame2'
});
var button2 = $('<button />', {
text: 'Child Button',
click: iterate
});
var button = $('<button />', {
text: 'Click Me',
click: function() {
$(this).parents('body').append(iFrame2);
$('#iFrame1').contents().find('#iFrame2').contents().find('body').append(button2);
}
});
setTimeout(function() {
$('.container').append(iFrame1);
$('#iFrame1').contents().find('body').append(button);
}, 1000);
});
Sorry if I misunderstood but if you just wanna log the number of clicks on the window, you could use
window.onclick = function() {count++;console.log(count);};

Error: cannot call methods on tabs prior to initialization; attempted to call method 'destroy'

I'm working on updating an older site to the newer version of jQuery and applying bootstrap.
We are using
backbone.js
jQueryui and
bootstrap.js.
I am getting the following error: Error: cannot call methods on tabs prior to initialization; attempted to call method 'destroy'
The lines of code it is related to is:
Thanks
define([
'jquery',
'jqueryui',
'underscore',
'backbone',
'vm',
'events',
'models/product',
'text!templates/editor/page.html'
], function ($, jui, _, Backbone, Vm, Events, Product, PageTemplate) {
var EditorPage = Backbone.View.extend({
model: Product,
el: '#editor',
_$editorErrorContainer: undefined,
events: {
"click #toolbar": "onToolbarClicked"
},
initialize: function () {
this.bindSaveEvents();
Events.unbind("addText");//HACK: Prevents zombie listeners for this specific situation
Events.bind("addText", this.onAddText, this);
},
onAddText: function (e) {
var layers = this.model.get('layers');
var textCollections = [];
//Search for valid text collections in our layer and push into our array
layers.models.forEach(function (layer) {
var tc = layer.get("textCollection");
if (typeof tc !== "undefined") {
if (layer.get("allowText")) {
var printColor = layer.get("printColor");
var src;
if(printColor) { src = printColor.get("src"); }
var _tc = { 'textCollection': tc, 'src': src, 'name':layer.get('name'), 'cid': layer.cid };
textCollections.push(_tc);
}
}
});
//if there's no choice to make, just add the text element.
if (textCollections.length == 1) {
var textCollection = textCollections[0].textCollection;
AddTextToCollection(textCollection);
return;
}
//Otherwise create dialog for choosing in which text collection to put a new text object
var dialogHTML = "<div> <p>What color would you like this text?</p>";
//Generate selections, use the src attribute of the print color if it uses printcolor
//TODO use other attributes of a textCollection such as rgb color if it uses it etc
textCollections.forEach(function (textCollection, i) {
var imgurl = "";
if(textCollection.src) {
imgurl = "/productEditor/assets/printcolors/icons/" + textCollection.src;
}
//Customers wont like zero indexed option names
var id = i + 1;
dialogHTML += "<p data-id= " + i + " class='selection'> Group " + id + ": " + textCollection.name + "<img src='" + imgurl + "'/> </p>";
});
dialogHTML += "</div>"
$(dialogHTML).dialog({
modal: true,
width: "25%",
height: "auto",
dialogClass: "textAddDialog",
resizable: false,
position: {
my: 'left top',
at: 'left top',
of: $("#editor"),
collision: 'flip'
},
show: 'fade',
create: function() {
var that = this;
//bind dialog events
$(this).children(".selection").click(function(){
$(that).children('.selected').removeClass("selected");
$(this).addClass("selected");
});
},
open: function (event, ui) {
$('.ui-dialog').css('z-index',2003);
$('.ui-widget-overlay').css({
'z-index': 2002,
'opacity': 0.5
});
},
close: function() {
$(this).children(".selection").unbind();
$(this).dialog('destroy').remove();
},
buttons: [{
text: "Accept",
"class": "acceptButton",
click: function() {
var index = $(this).children('.selected').data('id');
if(index >= 0) {
var textCollection = textCollections[index].textCollection;
AddTextToCollection(textCollection);
}
$(this).dialog("close");
}
},
{
text: "Cancel",
click: function() {
$(this).dialog("close");
}
}
]
});
function AddTextToCollection (textCollection) {
//Create text element in chosen group
var entry = textCollection.createEntry();
//Show user that this is a fresh text element that needs changing
entry.set("text", "Edit Me!");
entry.set("freshElement", true);//Fresh element denotes a completely new element, used to delete this element if user adds an element -> cancel button
//Trigger mouseup on element so our text editor dialog will pop for this element
var elem = $('.text_modifier[data-cid="' + entry.cid + '"]');
elem.trigger("click");
}
},
bindSaveEvents: function(){
Events.on('saveError', this.onSaveError.bind(this));
Events.on('saveSuccess', this.onSaveSuccess.bind(this));
},
unbindSaveEvents: function(){
Events.off('saveError', this.onSaveError.bind(this));
Events.off('saveSuccess', this.onSaveSuccess.bind(this));
},
onToolbarClicked: function (e) {
//console.log("toolbar clicked");
//this._productView.clearSelectedChildren();
},
onSaveError: function(errorMessage){
// if(this._$editorErrorContainer === undefined){
// this._$editorErrorContainer = $("<div/>").addClass("error");
// $(this.el).before(this._$editorErrorContainer);
// }
// this._$editorErrorContainer.text(errorMessage).show();
$('#appError').text(errorMessage).show();
},
onSaveSuccess: function(){
// if(this._$editorErrorContainer != null){
// this._$editorErrorContainer.hide();
// }
$('#appError').hide();
},
onRenderComplete: function (e) {
// ADD THE RETURN ELEMENT TO THE LIST OF
this._loadList = _.without(this._loadList, e);
if (this._loadList.length == 0) {
$(this.el).removeClass('loading');
}
},
remove: function(){
this.unbindSaveEvents();
},
renderApp: function () {
var that = this;
// PUT LIST OF ITEMS THAT RENDER INTO ARRAY
// renderComplete WILL REMOVE ITEMS, AND CHANGE STATE TO VISIBLE WHEN ALL ARE LOADED
this._loadList = ['layer', 'product', 'modifiers', 'tabs']
// CREATE PREVIEW MODIFIERS
require(['views/modifiers/product'], function (PreviewModifierView) {
that._modifierView = new PreviewModifierView({ model: that.model });
that._modifierView.on("renderComplete", that.onRenderComplete, that);
that._modifierView.render();
});
// CREATE PREVIEW
require(['views/preview/product'], function (PreviewProductView) {
that._productView = new PreviewProductView({ model: that.model });
that._productView.on("renderComplete", that.onRenderComplete, that);
that._productView.render();
});
// CREATE PRODUCT NAME DISPLAY
require(['views/toolbar/productname'], function (ProductNameView) {
that._productNameView = new ProductNameView({ model: that.model });
that._productNameView.render();
});
// CREATE PRODUCT DESCRIPTION DISPLAY
require(['views/toolbar/productdescription'], function (ProductDescView) {
that._productDescriptionView = new ProductDescView({ model: that.model });
that._productDescriptionView.render();
});
// CREATE TOOLBAR
require(['views/toolbar/layer'], function (ToolbarLayerView) {
that._toolbarView = new ToolbarLayerView({ model: that.model });
that._toolbarView.on("renderComplete", that.onRenderComplete, that);
that._toolbarView.render();
});
// CREATE FINISHED BUTTON
require(['views/toolbar/finished'], function (FinishedView) {
that._finishedView = new FinishedView({ model: that.model });
that._finishedView.render();
});
// CREATE TABS
require(['views/tabs/product'], function (TabsView) {
that._tabsView = new TabsView({ model: that.model });
that._tabsView.on("renderComplete", that.onRenderComplete, that);
that._tabsView.render();
});
},
render: function () {
//console.log("editor render!");
//console.log(this.model);
//$('#productName').html(this.model.get);
var pageTemplate = _.template(PageTemplate, this);
$(this.el).html(pageTemplate);
$('#button-editor-help').show(); // GET'S HIDDEN ON THE APPROVAL PAGE
this.renderApp();
}
});
return EditorPage;
});
In the tabs view, hook up a debugger where you call the destroy and create
Replicate the bug and check if you are calling destroy before initialization
May be you are calling close before the dialog is initialised.

Input tags inside backgrid table

I have created in backgridjs table custom "TagCell" (with implemented THIS).
So my cell looks like:
var TagCell = Backgrid.TagCell = Cell.extend({
className: "tag-cell",
events: {
'click .tag a': 'removetag',
},
initialize: function (options) {
TagCell.__super__.initialize.apply(this, arguments);
this.title = options.title || this.title;
this.target = options.target || this.target;
var model = this.model;
var rawData = this.formatter.fromRaw(model.get(this.column.get("name")), model);
},
removetag: function(event) {
var that = this;
that.model.set({location: ""},{success: alert("removed!"));
},
render: function () {
this.$el.empty();
var rawValue = this.model.get(this.column.get("name"));
var formattedValue = this.formatter.fromRaw(rawValue, this.model);
this.$el.append('<input name="location" class="tagggs" value="'+formattedValue+'" />');
this.delegateEvents();
return this;
},
});
If I trying to call removetag function with event click to ".tag" model with empty location is saved. But If I trying to call function with click event to ".tag a" or directly to class ".rmvtag" function is not called. I think because jquery tags input is designed like this:
$('<span>').addClass('tag').append(
$('<span>').text(value).append(' '),
$('<a>', {
href : '#',
class : 'rmvtag',
text : 'x'
}).click(function () {
return $('#' + id).removeTag(escape(value));
})
).insertBefore('#' + id + '_addTag');
So there is click function with removetag() written directly after append element. How can I call save model function from backbone on click to rmvtag link?
Thanks for any help!

Dynamically add UI Elements in CKEditor Dialog

I'm trying to trigger a callback to dynamically populate a CKEditor dialog with checkboxes when the dialog is opened. I've read other solutions that use iframes, but this won't work for me because the dialog needs to be populated based on other elements on the same page.
Here is what I have so far. There are no errors, but the dialog is just empty when it opens. I expect the addContents function to fill in the dialog. I've confirmed that dialog.definition.contents does include the contents and elements that I want, but it's just not filling in the actual dialog. What am I missing?
(function() {
CKEDITOR.plugins.add( 'embeds', {
icons: 'embed',
init: function(editor) {
var self = this,
elements = [];
CKEDITOR.dialog.add('EmbedsDialog', function (instance) {
return {
title : 'Embeds',
minWidth : 550,
minHeight : 200,
contents: [],
onShow: function() {
var dialog = this,
elements = [];
$('#embeds-fields tr').each(function() {
var title = $(this).find('input[type=text]').val(),
url = $(this).find('input[type=url]').val();
if(url != "") {
elements.push({
label : "embed",
title : url,
type : 'checkbox'
});
}
});
dialog.definition.removeContents('embeds');
dialog.definition.addContents({
id : 'embeds',
expand : true,
elements : elements
});
},
}; // return
});
editor.addCommand('Embeds',
new CKEDITOR.dialogCommand('EmbedsDialog', {
allowedContent: 'a[*](*)'
})
);
editor.ui.addButton('Embeds', {
label : 'Embeds',
command : 'Embeds',
toolbar : 'embeds'
});
} // init
}); // add
})(); // closure
Based off of this example, I ended up with this solution, where "main" is the ID of the original content.
CKEDITOR.on('dialogDefinition', function(ev) {
var dialogName = ev.data.name;
var dialogDefinition = ev.data.definition;
if (dialogName == 'EmbedsDialog') {
var main = dialogDefinition.getContents('main');
$('#embeds-fields tr').each(function() {
var title = $(this).find('input[type=text]').val(),
url = $(this).find('input[type=url]').val();
if(url != "") {
main.add({
type : 'checkbox',
label : title,
});
}
});
}
});

Categories

Resources