I have simple JavaScript snippet:
var obrazek = [{nazwa: "Sniadanie", wiek: 100, autor: "Alicja"},{nazwa: "Kolacja", wiek: 10, autor: "Misiek"}];
function galeria(nazwa, wsad) {
this.nazwa = nazwa;
this.wsad = wsad;
this.print = function(element) {
for (var i=0;i<this.wsad.length;i++) {
var text = "<li>"+this.wsad[i].nazwa+"</li>"
element.append(text);
}
}
}
$(document).ready(function() {
gal = new galeria('test', obrazek);
gal.print($('#galeriaTest'))
});
It gives me:
<ul id="galeriaTest>
<li>Sniadanie</li>
<li>Kolacja</li>
</ul>
What I want is simple method that will return object after click event:
Object { nazwa="Sniadanie", wiek=100, autor="Alicja"} (in FireBug)
How to code it?
As long as your data set is static, you can just associate the object to the DOM element using the data() function.
Here's an example.
If your data set is dynamic, you could still associate a reference to the Galeria and some ID type of information to get a similar albeit improved result.
$("selector").on('click', function(e){
console.log( obrazek ); // would put object in a console, you can check it via firebug
});
Related
I have multiple toggles being called at the same time during a click /eventListener and it works but I'm wondering if there is a cleaner way to write the code where it doesn't look so repetitive.
Here is a snippet of the code I'm trying to refactor/clean up. Any help or point in the right direction is greatly appreciated. Thanks!
document.getElementById("menu-toggle").addEventListener("click", mobileMenuToggle);
function mobileMenuToggle() {
var hideMenu = document.getElementById("mobile-menu-container");
hideMenu.classList.toggle("hide-menu");
var hideMenuIcon = document.getElementById("menu-toggle");
hideMenuIcon.classList.toggle("hide-menu-icon");
var closeMenuIcon = document.getElementById("close-menu-toggle");
closeMenuIcon.classList.toggle("show-close-icon");
var closeMenuIcon = document.getElementById("container-bg-toggle");
closeMenuIcon.classList.toggle("show-icon-bg");
}
Few other options
To make it more concise, you can get rid of references, which you are not reusing
function mobileMenuToggle() {
document.getElementById("mobile-menu-container").classList.toggle("hide-menu");
document.getElementById("menu-toggle").classList.toggle("hide-menu-icon");
document.getElementById("close-menu-toggle").classList.toggle("show-close-icon");
document.getElementById("container-bg-toggle").classList.toggle("show-icon-bg");
}
In case if these elements are nested, You can just write a css rule and do one toggle
#mobile-menu-container.hide-menu .menu-toggle {
display:none;
}
#mobile-menu-container.hide-menu .close-menu-toggle {
display:block;
}
And you can just toggle the parent container alone
function mobileMenuToggle() {
document.getElementById("mobile-menu-container").classList.toggle("hide-menu");
}
In case if you are using jquery, you can use the jquery toggle, which is bit short
$('#mobile-menu-container').toggleClass('hide-menu');
I would write something like this, put all element info in a object, iterate the object and do DOM manipulation. This way a new element-class pair can be added in the object, rest of the code will be untouched
function mobileMenuToggle() {
// Object to store id-class name pair
var elements = {"mobile-menu-container" : "hide-menu",
"menu-toggle": "hide-menu-icon",
"close-menu-toggle" : "show-close-icon",
"container-bg-toggle": "show-icon-bg"};
// iterate and toggle class names
for (var elementId in elements ) {
var el = document.getElementById(elementId );
el.classList.toggle(elements[elementId]);
}
}
There are couple of ways of doing it based on how clean you want and how many repetitive elements are there.
If you have just few elements, then this should be clean enough.
var menuToggle = document.getElementById("menu-toggle");
function mobileMenuToggle() {
var hideMenu = document.getElementById("mobile-menu-container");
var hideMenuIcon = document.getElementById("menu-toggle");
var closeMenuIcon = document.getElementById("close-menu-toggle");
var closeMenuBgIcon = document.getElementById("container-bg-toggle");
hideMenu.classList.toggle("hide-menu");
hideMenuIcon.classList.toggle("hide-menu-icon");
closeMenuIcon.classList.toggle("show-close-icon");
closeMenuBgIcon.classList.toggle("show-icon-bg");
}
menuToggle.addEventListener("click", mobileMenuToggle);
If you have many elements and they need to be dynamic then you can convert it to an array and loop over them.
var menuToggle = document.getElementById("menu-toggle")
var elementList = [
{ id: "mobile-menu-container", className: "hide-menu" },
{ id: "hide-menu", className: "hide-menu-icon" },
{ id: "close-menu-toggle", className: "show-close-icon" },
{ id: "container-bg-toggle", className: "show-icon-bg" }
];
function mobileMenuToggle() {
elementList.forEach(function (props) {
var element = document.getElementById(props.id);
element.classList.toggle(props.className);
});
}
menuToggle.addEventListener("click", mobileMenuToggle);
I would like to use a javascript loop to create multiple HTML wrapper elements and insert JSON response API data into some of the elements (image, title, url, etc...).
Is this something I need to go line-by-line with?
<a class="scoreboard-video-outer-link" href="">
<div class="scoreboard-video--wrapper">
<div class="scoreboard-video--thumbnail">
<img src="http://via.placeholder.com/350x150">
</div>
<div class="scoreboard-video--info">
<div class="scoreboard-video--title">Pelicans # Bulls Postgame: E'Twaun Moore 10-8-17</div>
</div>
</div>
</a>
What I am trying:
var link = document.createElement('a');
document.getElementsByTagName("a")[0].setAttribute("class", "scoreboard-video-outer-link");
document.getElementsByTagName("a")[0].setAttribute("url", "google.com");
mainWrapper.appendChild(link);
var videoWrapper= document.createElement('div');
document.getElementsByTagName("div")[0].setAttribute("class", "scoreboard-video-outer-link");
link.appendChild(videoWrapper);
var videoThumbnailWrapper = document.createElement('div');
document.getElementsByTagName("div")[0].setAttribute("class", "scoreboard-video--thumbnail");
videoWrapper.appendChild(videoThumbnailWrapper);
var videoImage = document.createElement('img');
document.getElementsByTagName("img")[0].setAttribute("src", "url-of-image-from-api");
videoThumbnailWrapper.appendChild(videoImage);
Then I basically repeat that process for all nested HTML elements.
Create A-tag
Create class and href attributes for A-tag
Append class name and url to attributes
Append A-tag to main wrapper
Create DIV
Create class attributes for DIV
Append DIV to newly appended A-tag
I'd greatly appreciate it if you could enlighten me on the best way to do what I'm trying to explain here? Seems like it would get very messy.
Here's my answer. It's notated. In order to see the effects in the snippet you'll have to go into your developers console to either inspect the wrapper element or look at your developers console log.
We basically create some helper methods to easily create elements and append them to the DOM - it's really not as hard as it seems. This should also leave you in an easy place to append JSON retrieved Objects as properties to your elements!
Here's a Basic Version to give you the gist of what's happening and how to use it
//create element function
function create(tagName, props) {
return Object.assign(document.createElement(tagName), (props || {}));
}
//append child function
function ac(p, c) {
if (c) p.appendChild(c);
return p;
}
//example:
//get wrapper div
let mainWrapper = document.getElementById("mainWrapper");
//create link and div
let link = create("a", { href:"google.com" });
let div = create("div", { id: "myDiv" });
//add link as a child to div, add the result to mainWrapper
ac(mainWrapper, ac(div, link));
//create element function
function create(tagName, props) {
return Object.assign(document.createElement(tagName), (props || {}));
}
//append child function
function ac(p, c) {
if (c) p.appendChild(c);
return p;
}
//example:
//get wrapper div
let mainWrapper = document.getElementById("mainWrapper");
//create link and div
let link = create("a", { href:"google.com", textContent: "this text is a Link in the div" });
let div = create("div", { id: "myDiv", textContent: "this text is in the div! " });
//add link as a child to div, add the result to mainWrapper
ac(mainWrapper, ac(div, link));
div {
border: 3px solid black;
padding: 5px;
}
<div id="mainWrapper"></div>
Here is how to do specifically what you asked with more thoroughly notated code.
//get main wrapper
let mainWrapper = document.getElementById("mainWrapper");
//make a function to easily create elements
//function takes a tagName and an optional object for property values
//using Object.assign we can make tailored elements quickly.
function create(tagName, props) {
return Object.assign(document.createElement(tagName), (props || {}));
}
//document.appendChild is great except
//it doesn't offer easy stackability
//The reason for this is that it always returns the appended child element
//we create a function that appends from Parent to Child
//and returns the compiled element(The Parent).
//Since we are ALWAYS returning the parent(regardles of if the child is specified)
//we can recursively call this function to great effect
//(you'll see this further down)
function ac(p, c) {
if (c) p.appendChild(c);
return p;
}
//these are the elements you wanted to append
//notice how easy it is to make them!
//FYI when adding classes directly to an HTMLElement
//the property to assign a value to is className -- NOT class
//this is a common mistake, so no big deal!
var link = create("a", {
className: "scoreboard-video-outer-link",
url: "google.com"
});
var videoWrapper = create("div", {
className: "scoreboard-video-outer-link"
});
var videoThumbnailWrapper = create("div", {
className: "scoreboard-video--thumbnail"
});
var videoImage = create("img", {
src: "url-of-image-from-api"
});
//here's where the recursion comes in:
ac(mainWrapper, ac(link, ac(videoWrapper, ac(videoThumbnailWrapper, videoImage))));
//keep in mind that it might be easiest to read the ac functions backwards
//the logic is this:
//Append videoImage to videoThumbnailWrapper
//Append (videoImage+videoThumbnailWrapper) to videoWrapper
//Append (videoWrapper+videoImage+videoThumbnailWrapper) to link
//Append (link+videoWrapper+videoImage+videoThumbnailWrapper) to mainWrapper
let mainWrapper = document.getElementById('mainWrapper');
function create(tagName, props) {
return Object.assign(document.createElement(tagName), (props || {}));
}
function ac(p, c) {
if (c) p.appendChild(c);
return p;
}
var link = create("a", {
className: "scoreboard-video-outer-link",
url: "google.com"
});
var videoWrapper = create("div", {
className: "scoreboard-video-outer-link"
});
var videoThumbnailWrapper = create("div", {
className: "scoreboard-video--thumbnail"
});
var videoImage = create("img", {
src: "url-of-image-from-api"
});
ac(mainWrapper, ac(link, ac(videoWrapper, ac(videoThumbnailWrapper, videoImage))));
//pretty fancy.
//This is just to show the output in the log,
//feel free to just open up the developer console and look at the mainWrapper element.
console.dir(mainWrapper);
<div id="mainWrapper"></div>
Short version
Markup.js's loops.
Long version
You will find many solutions that work for this problem. But that may not be the point. The point is: is it right? And you may using the wrong tool for the problem.
I've worked with code that did similar things. I did not write it, but I had to work with it. You'll find that code like that quickly becomes very difficult to manage. You may think: "Oh, but I know what it's supposed to do. Once it's done, I won't change it."
Code falls into two categories:
Code you stop using and you therefore don't need to change.
Code you keep using and therefore that you will need to change.
So, "does it work?" is not the right question. There are many questions, but some of them are: "Will I be able to maintain this? Is it easy to read? If I change one part, does it only change the part I need to change or does it also change something else I don't mean to change?"
What I'm getting at here is that you should use a templating library. There are many for JavaScript.
In general, you should use a whole JavaScript application framework. There are three main ones nowadays:
ReactJS
Vue.js
Angular 2
For the sake of honesty, note I don't follow my own advice and still use Angular. (The original, not Angular 2.) But this is a steep learning curve. There are a lot of libraries that also include templating abilities.
But you've obviously got a whole project already set up and you want to just plug in a template into existing JavaScript code. You probably want a template language that does its thing and stays out of the way. When I started, I wanted that too. I used Markup.js . It's small, it's simple and it does what you want in this post.
https://github.com/adammark/Markup.js/
It's a first step. I think its loops feature are what you need. Start with that and work your way to a full framework in time.
Take a look at this - [underscore._template]
It is very tiny, and useful in this situation.
(https://www.npmjs.com/package/underscore.template).
const targetElement = document.querySelector('#target')
// Define your template
const template = UnderscoreTemplate(
'<a class="<%- link.className %>" href="<%- link.url %>">\
<div class="<%- wrapper.className %>">\
<div class="<%- thumbnail.className %>">\
<img src="<%- thumbnail.image %>">\
</div>\
<div class="<%- info.className %>">\
<div class="<%- info.title.className %>"><%- info.title.text %></div>\
</div>\
</div>\
</a>');
// Define values for template
const obj = {
link: {
className: 'scoreboard-video-outer-link',
url: '#someurl'
},
wrapper: {
className: 'scoreboard-video--wrapper'
},
thumbnail: {
className: 'scoreboard-video--thumbnail',
image: 'http://via.placeholder.com/350x150'
},
info: {
className: 'scoreboard-video--info',
title: {
className: 'scoreboard-video--title',
text: 'Pelicans # Bulls Postgame: E`Twaun Moore 10-8-17'
}
}
};
// Build template, and set innerHTML to output element.
targetElement.innerHTML = template(obj)
// And of course you can go into forEach loop here like
const arr = [obj, obj, obj]; // Create array from our object
arr.forEach(item => targetElement.innerHTML += template(item))
<script src="https://unpkg.com/underscore.template#0.1.7/dist/underscore.template.js"></script>
<div id="target">qq</div>
I would like to re-use an "object", however, one of the object's properties values should be recalculated every time the object is accessed.
In my code I have a library which can basically make a list of card views from a data url. This list of card views is added to a page. There are two types of lists: Active Buildings list and Archived Buildings list. Switching between these two lists is done by pressing a button, which triggers the "rerender" function of the repeater shown below.
Archived Buildings should not be clickable. I pass along some configuration options to my library where I handle the relevant parts. However, because of the way I invoke the card view library, the value of the enableClick configuration option is always set to what the state was like at the load of the page.
Example of how the code looks:
$(function () {
var buildingsContainer = $('#buildings');
buildingsContainer.repeater({
url: function () {
var activeFilter = buildingFilter.find('.btn-primary').data('status');
return '/Building/All?status=' + activeFilter;
},
renderItem: cardTemplates(buildingsContainer).building({
activateBuildingUrl: '#(Url.Action("ActivateBuilding", "Building"))/{Id}',
editUrl: '#(Url.Action("Edit", "Building"))/{Id}',
deleteBuildingUrl: '#(Url.Action("SoftDeleteBuilding", "Building"))/{Id}',
enableClick: getActiveFilter() === 'Active'
})
})
});
function getActiveFilter() {
var buildingFilter = $('#buildingFilter');
return buildingFilter.find('.btn-primary').data('status');
}
No matter what the currently pressed button is, enableClick is always set to what it was when the page opened.
To better demonstrate my problem, I have created a JSFiddle:
https://jsfiddle.net/e3xnbxov/
In this JSFiddle, you see I have a options object with a value property. In the button's click listeners I print this value. However, it always remains on Active, even though I switch between Active and Archived. How can I make it so the value of the property is recalculated?
I think you have 2 options here.
1) Set the property as a function, and evaluate it:
$(function() {
var options = {
value: ()=>$('#container').find('.btn-primary').data('status')
};
var container = $('#container');
container.find('.btn').click(function() {
container.find('.btn').removeClass('btn-primary').addClass('btn-default');
$(this).addClass('btn-primary');
console.log(options.value());
});
});
jsfiddle: https://jsfiddle.net/mw8kuq6L/
2) Just use "this" to directly access the data value you want to check:
$(function() {
var container = $('#container');
container.find('.btn').click(function() {
container.find('.btn').removeClass('btn-primary').addClass('btn-default');
$(this).addClass('btn-primary');
console.log($(this).data('status'));
});
});
The problem is that the object (options) is created once, and the property is set once.
At the moment that the creation (and property setting) occurs, the 'active' button matches the jQuery selector ($('#container').find('.btn-primary')).
Javascript, like many languages, uses references. When you set the object's property, it received a reference to the result of the jQuery selector, not the selector (as a method) itself.
You could change it to behave more as you're expecting by creating a method on your object:
$(function() {
var options = {
value: function () {
return $('#container').find('.btn-primary').data('status')
}
};
var container = $('#container');
container.find('.btn').click(function() {
container.find('.btn').removeClass('btn-primary').addClass('btn-default');
$(this).addClass('btn-primary');
console.log(options.value());
});
});
Thus your options object now has a callable method which dynamically returns what you were expecting.
Otherwise I'd update the property when the selected button changes:
$(function() {
var options = {
value: $('#container').find('.btn-primary').data('status')
};
var container = $('#container');
container.find('.btn').click(function() {
container.find('.btn').removeClass('btn-primary').addClass('btn-default');
$(this).addClass('btn-primary');
options.value = $('#container').find('.btn-primary').data('status');
console.log(options.value);
});
});
This is just meant to be an addition to lpg's answer.
Another way would be to use a getter function which behaves like lpg's value function but can be used like a normal property:
$(function() {
var options = {
// define a getter for the property 'value'
get value () {
return $('#container').find('.btn-primary').data('status');
}
};
var container = $('#container');
container.find('.btn').click(function() {
container.find('.btn').removeClass('btn-primary').addClass('btn-default');
$(this).addClass('btn-primary');
console.log(options.value); // use the property for the property 'value'
});
});
<link href="http://netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css" rel="stylesheet"/>
<link href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap-theme.min.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="http://netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js"></script>
<div id="container">
<button class="btn btn-sm btn-primary" data-status="Active">Active</button>
<button class="btn btn-sm btn-default" data-status="Archived">Archived</button>
</div>
Thanks in advance. Please excuse me for my grammer. I tried my best to explain my issue In my quest of solving below question I started to develop a POC first.
C# MVC No submit pass object between views
I am having an issue using TempData object and pass my model between my parent popup and child popup. My problem is I am doing TempData["StudentViewModel"] 2 times. First time insert and First time read are good but Second time read even though I make sure that I insert second time before read is not working.
I will try my best to explain it clearly.
I have a ASP.NET page called Class.cshtml. It will have a grid of all class. The user will select a ClassName column and it opens up Students.cshtml as a new popup window which has a grid with StudentName and Address columns. The user will select StudentName and it opens another popup window called StudentDetails.cshtml.
We have a ClassController.cs which is used by all popups and have C# methods. ClassController.js has all javscript code.
public ActionResult GetStudentsDetails()
{
// this will create students for each class.
//Inside student for each class it will also create Student Details.
// First Insert
TempData["StudentViewModel"] = studentViewModel;
return View("Students", studentViewModel);
}
Students.cshtml is an existing popup window like below
<div>
//this is in for loop
string anchorElementId = string.Format("AnchorElementId_{0}", i);
string selectedIndex = i.ToString();
string name = Model.Students[i].Name;
<input class="ButtonLikeHyperLink"
id="myBtnId"
onclick="ShowStudentDetails(#selectedIndex, '#name', '#anchorElementId')"
value="#Model.Students[i].Name"
type="button"/>
//for loop ends here
//First read
<span id="lblHDNStudentViewModel">
#Newtonsoft.Json.JsonConvert.SerializeObject(TempData["StudentViewModel"] as StudentViewModel)
</span>
</div>
Once user selects any StudentName in Students.cshtml popup the below js method is called which opens a Child window popup having particular student details.
ClassController.js
function ShowStudentDetails(selectedIndex, name, anchorElementId)
{
var inputParam = {};
var hiddenField = document.getElementById("lblHDNStudentViewModel");
if (hiddenField != null)
{
inputParam.StudentVM = JSON.parse(hiddenField.innerText);
inputParam.selectedIndex = selectedIndex;
inputParam.name = name;
inputParam.anchorElementId = anchorElementId;
// __callback is our custom method to call controller action method
var retVal = __callback("OnNameSelected", inputParam);
var postedData = JSON.parse(retVal.return_value);
if (postedData.Success == true)
{
// i need to do like below since Model to my popup is dynamic
multipleMatchPopup = window.open('', '', properties);
multipleMatchPopup.document.write(postedData.PartialViewHtml);
}
}
}
ClassController.cs
public JsonResult OnNameSelected(StudentViewModel StudentVM, int selectedIndex, string name, string anchorElementId)
{
// this will create student name details viewmodel for selected name and modify StudentViewModel object.
// for example
StudentDetailsViewModel vm = StudentVM[selectedIndex].DetailsVM;
//since user made selection update few properties in vm
StudentVM[selectedIndex].DetailsVM = vm;
//Second insert
// make sure to set tempdata before RenderPartialViewToString
TempData["StudentViewModel"] = StudentVM;
string sHtml = this.RenderPartialViewToString("~/Views/_PartialStudentDetailsPopup.cshtml", vm);
return Json(new
{
Success = true,
data = StudentVM,
PartialViewHtml = sHtml,
JsonRequestBehavior.AllowGet
});
}
In StudentDetails.cshtml popup I have like this
<div>
.....
<input class="ButtonLikeHyperLink"
id="#buttonId"
onclick="OnUserSelectStudentDetails()"
value="[Select]"
type="button" />
//Second read
//in fiddler innertext is show as null
<span id="lblHDNStudentDetailsViewModel">
#Newtonsoft.Json.JsonConvert.SerializeObject(TempData["StudentViewModel"] as StudentViewModel)
</span>
</div>
ClassController.js
function OnUserSelectStudentDetails()
{
var inputParam = {};
var hiddenField = document.getElementById("lblHDNStudentDetailsViewModel");
if (hiddenField != null)
{
//hiddenField.innerText is null
inputParam.StudentVM = JSON.parse(hiddenField.innerText);
var retVal = __FAFdoCallback("OnUserSelectLenderMatchingFee", inputParam);
...
}
}
ClassController.cs
public JsonResult OnUserSelectLenderMatchingFee(StudentViewModel StudentVM)
{
//StudentVM is null here
}
UPDATE
SOLUTION
I feel real stupid on this issue. As the great detective, Hercule Poirot said, "The great gray cells are not working", mine also did not work
in this case. Sometimes we think so far away from the box that we oversee the basics. I was thinking that this thing cannot be done in so simple so I was thinking about TempData and so on and forgot the fundamental point that my parent popup already have a hidden field and I can read from it and write to it it in my javascript methods of parent and child popups windows and pass it to controller action methods and get back updated and consistent model back.
Taking this basic solution this is what I did
Students.cshtml
<span id="lblHDNStudentViewModel">
#Newtonsoft.Json.JsonConvert.SerializeObject(Model)
</span>
Read this in parent window javascript method like below in ClassController.js
function ShowStudentDetails(selectedIndex, name, anchorElementId)
{
var inputParam = {};
//read
var hiddenField = document.getElementById("lblHDNStudentViewModel");
}
Read this from child window javascript method like below in ClassController.js
function OnUserSelectStudentDetails()
{
var inputParam = {};
// read in child window and access parent window element
var hiddenField = window.opener.document.getElementById("lblHDNStudentViewModel");
}
Write back to parent window element from parent window javascript method like below
document.getElementById("lblHdnCDLenderViewModel").innerText = JSON.stringify(postedData.data);
Write back to parent window element from child window javascript method like below
window.opener.document.getElementById("lblHdnCDLenderViewModel").innerText = JSON.stringify(postedData.data);
I'm trying to create my own lightbox script where I can pass the variables (title, description, itemtype, itemid, etc.) in clean formatting like this (inspired by fancybox):
myFunction({
title: "My title",
description: "My description"
});
Clicking on a certain element prepends some HTML to a div with jQuery.
I have adapted a piece of code I found on Stackoverflow and "kind of" understand the code. The top function has not been changed and worked before I edited the bottom code, to that I added click(function() { } because in the example the code was executed on pageload.
However, when I click my H1 element the firebug console tells me ReferenceError: popup is not defined
This is my Javascript:
$(document).ready(function() {
(function ($) {
$.fn.popup = function (options) {
var settings = $.extend({
title: function (someData) {
return someData;
},
description: function (someData) {
return someData;
},
}, options);
$("#content").prepend(
"<div style=\"position:fixed;top:0;left:0;width:100%;height:100%;background:#FFFFFF;\">\
<h1>"+ settings.title +"</h1>\
<p>" + settings.description +"</p>\
</div>"
);
};
}(jQuery));
$(".openbox1").click(function() {
popup({
title: "Title 1",
description: "Description 1"
});
}));
$(".openbox2").click(function() {
popup({
title: "Title 2",
description: "Description 2"
});
}));
});
This is my HTML
<div id="content">
<h1 class="openbox1">open box 1</h1>
<h1 class="openbox2">open box 2</h1>
</div>
A. Wolff commented that I need to execute the function like this:
$(".openbox1").click(function() {
$(this).popup({
...
});
});
This fixed it, thanks!
First off, what you did, and I hope this helps:
// This, of course is same as "document.onload"
// Don't confuse it with "window.onload"
// wich will wait till WHOLE dom is loaded to run any script
$(document).ready(function() {
(function ($) {
// This is, in essence, the start of a jQuery plugin
// This is often referred to as the "quick and dirty setup"
// as it's a direct call to add a method to jQuery's
// element object. Meaning it can be recalled as
// $(element).popup().
// This should not be confused with $.popup = function
// which would just add a method to jQuery's core object
$.fn.popup = function (options) {
var settings = $.extend({
...
}(jQuery));
$(".openbox1").click(function() {
// here is where your issue comes in
// as previously noted, you did not create a
// method named "popup".
// you added a method to jQuery's Element Object
// called "popup".
// This is why `$(this).popup` works and
// plain `popup` does not.
// You're inside an "event" asigned to any element
// having class name `openbox1`. Thus, any call
// in here to `this`, will reference that element
popup({
Secondly, a different example of how to write it. I won't say better because, even if I say my way is better, it wouldn't make your "corrected" way wrong. In Javascript, as the old saying goes, There's more than one way to skin a cat.
My Example:
// Notice I'm adding this plugin BEFORE the document load.
// This means, you could easily add this to a file and load it
// in script tags like any other Javascript,
// as long as it's loaded AFTER jquery.
(function($) {
// this ensures that your plugin name is available and not previously added to jQuery library
if (!$.popup) {
// this also provides us "variable scope" within to work in
// here begin adding the plugin to jQuery
// I started with $.extend, so it can be added to the jQuery library and used in traditional format
// $.popup('element selector', { options })
// as well as the element.action format we'll add later
// $.(element selector).popup({ options })
// This should help give you a good idea of the whole of what all is going on
$.extend({
popup: function() {
var ele = arguments[0], // this is our jQuery element
args = Array.prototype.slice.call(arguments, 1); // this gets the rest of the arguments
// this next step is useful if you make the traditional call `$.popup(this, { options })`
if (!(ele instanceof jQuery)) ele = $(ele);
// now we have total control! Bwahahha!
// Fun aside, here is where it's good to check if you've already asigned this plugin
// if not, then make some "marker", so you can recall the element plugin and comment an
// action instead of reinitializing it
if (!ele.data('popup')) $.popup.init(ele, args);
else {
// at this point, you would know the element already has this plugin initialized
// so here you could change an initial options
// like how with jQueryUI, you might would call:
// $(element).popup('option', 'optionName', value)
}
return ele;
}
});
// here is where we add the $(element selector).popup method
// this simply adds the method to the element object
// If you don't fully understand what's going on inside (as I explain below),
// just know that it's some "fancy footwork" to pass the method onto our initial
// method creation, $.popup
$.fn.extend({
popup: function(/*no need for parameter names here as arguments are evaluated inside and passed on to initial method*/) {
// set this element as first argument to fit with initial plugin method
var args = [$(this)];
// if there are arguments/params/options/commands too be set, add them
if (arguments.length) for (x in arguments) args.push(arguments[x]);
// pass through jquery and our arguments, end result provides same arguments as if the call was:
// $.popup($(element), options)
return $.popup.apply($, args);
}
});
// This next part is not seen in many plugins but useful depending on what you're creating
$.popup.init = function(ele, opt) {
// here is where we'll handle the "heavy work" of establishing a plugin on this element
// Start with setting the options for this plugin.
// This means extending the default options to use any passed in options
// In the most simple of cases, options are passed in as an Oject.
// However, that's not always the case, thus the reason for this being
// a continued array of our arguments from earlier.
// We'll stick with the simplest case for now, your case, that the only options are an
// Object that was passed in.
// using the extend method, with true, with a blank object,
// allows us to added the new options "on top" of the default ones, without changing the default ones
// oh and the "true" part just tells extend to "dig deep" basically (multideminsional)
if (opt && typeof opt[0] == 'object') opt = $.extend(true, {}, $.popup.defaults, opt[0]);
var par = opt.parent instanceof jQuery ? opt.parent : $('body'),
tit = opt.title,
des = opt.description,
// this last one will be the wrapper element we put everything in
// you have this in yours, but it's written in a very long way
// this is jQuery simplified
wrap = $('<div />', { style: 'position:fixed;top:0;left:0;width:100%;height:100%;background:#FFFFFF;' }),
// much like the previous element, cept this is where our title goes
head = $('<h1 />', { text: tit }).appendTo(wrap),
content = $('<p />', { text: des }).appendTo(wrap);
$(par).append(wrap);
// finally, add our marker i mentioned earlier
ele.data('popup', opt);
// just adding the following cause i noticed there is no close
// fyi, i would change this plugin a little and make an actial "open" command, but that's another tutorial
var closer = $('<span />', { text: '[x]', style: 'cursor:pointer;position:absolute;bottom:1em;right:1em;' });
wrap.append(closer);
closer.click(function(e) { ele.data('popup', false); wrap.remove(); });
};
$.popup.defaults = { // establish base properties here that can be over-written via .props, but their values should never truly change
'parent': undefined, // added this to keep it dynamic, instead of always looking for an element ID'd as content
title: '',
description: ''
};
}
})(jQuery);
// the following is basically jQuery shorthand for document.ready
$(function() {
// i think you get the rest
$(".openbox1").on('click', function(e) {
$(this).popup({
title: "Title 1",
description: "Description 1",
parent: $("#content")
});
})
$(".openbox2").on('click', function(e) {
$(this).popup({
title: "Title 2",
description: "Description 2",
parent: $("#content")
});
})
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<div id="content">
<h1 class="openbox1">open box 1</h1>
<h1 class="openbox2">open box 2</h1>
</div>