I trying to customize HTML file input and wrote simplest jquery API function:
(function($)
{
$.fn.selectFileInputGUI = function()
{
var outerWrapper = $('<div>').css({'display':'inline-block'});
var innerWrapper = $('<div>').css({'width':'0', 'height':'0', 'overflow':'hidden'});
var fileInput = $('<input>').attr({type:'file'});
var fileNameHTML = $('<div>').css({'display':'inline-block', 'margin-left':'3px'});
var selectBtnGUI = $('<button>').addClass('btn btn-success btn-sm').text('Select file').click(function()
{
$.fn.selectFileInputGUI.resetInput();
fileInput.trigger('click');
});
fileInput.on('change', function()
{
$.fn.selectFileInputGUI.displayFileName();
});
$(this)
.append(outerWrapper.append(innerWrapper.append(fileInput)).append(selectBtnGUI))
.append(fileNameHTML);
$.fn.selectFileInputGUI.displayFileName = function()
{
var fileName = fileInput.val();
if(fileName.length > 0)
{
var pos = fileName.lastIndexOf("\\");
if(pos != -1) fileName = fileName.substr(pos + 1);
if(fileName.length > 14) fileName = fileName.substr(0, 14) + '...';
} else
{
fileName = 'File not selected';
}
fileNameHTML.text(fileName);
};
$.fn.selectFileInputGUI.resetInput = function()
{
fileInput.wrap('<form>').parent('form').trigger('reset');
fileInput.unwrap();
};
}
})(jQuery);
When I trying to apply selectFileInputGUI api function to the several selectors, only last selector handles well - http://jsfiddle.net/URKM5/5/
How to implement it correctly?
Looks like your problem is in how you reuse the variable fileInput and fileNameHTML, after user selecting the file, and you call this $.fn.selectFileInputGUI.displayFileName(); the fileInput and fileNameHTML always refer to the fileInput and fileNameHTML of the second fileDialog (because the second fileDialog is initialized after the first and all these variables are overridden). So to solve this problem, you have to pass these variables via the so-called event data, it's much helpful in this case:
//Note the second argument, it's passed in as event data
//You can access the event data via e.data
fileInput.on('change', fileNameHTML, function(e) {
$.fn.selectFileInputGUI.displayFileName($(this), e.data);
});
Your displayFileName need to accept 2 arguments, the first refers to the fileInput (which can be passed in as $(this) in the onchange event handler of the fileInput), the second refers to the fileNameHTML (which is passed in as e.data).
//note about the change of the arguments
$.fn.selectFileInputGUI.displayFileName = function(fileInput, fileNameHTML) {
//the code is unchanged here ...
}
Now selecting the files between the 2 dialogs is independent.
Demo.
After some deeper looking into the problem, looks like you don't need to use event data here. Just pass the fileNameHTML right as the second argument into displayFileName(...) function. Updated Demo
Related
I use the following function to dynamically load JavaScript:
function loadJS(file, c)
{
var jsEl = document.createElement("script");
jsEl.type = "application/javascript";
jsEl.src = file;
jsEl.async = false;
document.body.appendChild(jsEl);
if (c) { jsEl.addEventListener('load', function (e) { c(null, e); }, false); }
document.getElementsByTagName("head")[0].appendChild(jsEl);
}
Below is one of the functions contained within a dynamically added file:
function formelements_SELECT_add(x)
{
console.log("add");
alert("add");
var id = x[1];
var value = x[2];
var index = x[3];
var select = document.getElementById(id);
var option;
option = document.createElement("option");
option.text = value;
select.add(option, index);
}
I know the JavaScript file gets added correctly and the function gets executed because an option is added to the select element. Why does the alert and console.log not execute? If I click inspect, then there are no error messages.
EDIT
This is the code I use to call the function:
var typeAndElement = x[0][0] + "_" + x[0][1];
start = i + 1;
if (!window[typeAndElement])
{
loadJS("https://websemantica.org/scripts/" + x[0][0] + "/" + x[0][1] + ".js", continueManipulatingElements(typeAndElement, actions, x, start, total));
return;
}
else
{
fn = window[typeAndElement + "_" + x[0][2]];
if (typeof fn === 'function')
fn(x);
}
I didn't want to include it initially, because I already knew it was working and it will be unclear how it works considering it is using dynamic data.
Also, I have edited the loadJS function:
function loadJS(file, c)
{
var jsEl = document.createElement("script");
jsEl.type = "text/javascript";
jsEl.src = file;
jsEl.async = false;
if (c) { jsEl.addEventListener('load', function (e) { c(null, e); }, false); }
document.getElementsByTagName("head")[0].appendChild(jsEl);
}
The problem appears to be solved now.
Three things jump out:
You're appending the script to the page twice, once to document.body, and the other to document.getElementsByTagName("head")[0]. The second one will move the script from the first place to the second. You really only need to do one of those.
You're looking the load event after appending the element. If the script is in cache, you can miss the event. Hook it before appending the script to the DOM.
You're not doing anything in the quoted code to call the function that's defined by the script you're adding.
Other side notes:
No need to set type, the default is JavaScript.
No need to set async, all dynamically-inserted script elements are async (now, it wasn't always true).
As Jarosław Wlazło points out document.alert isn't a function; just alert is all you need. :-)
In addition: document has no alert method. It belongs to window, so you can call it like this alert('add');
What am I doing wrong, and how can one pass variables to a different function within the same wrapping variable/function.
Example:
function customFunctionWrap(){
this.myVar1 = 0;
this.getCurrentPosition = function(){
if (navigation.geolocation) {
navigator.geolocation.getCurrentPosition(function(position){});
}
},
this.doSomething = function(){ // Works
//Do something, return
this.callWithParams(); //Works
},
//If I remove passing in 'value1',calling it elsewhere works
this.doSomethingWithParams = function(value1){
//Use value1
//Return
},
this.callWithParams = function(){
var value1 = 'xyz'; //Is a variable that changes based on some DOM element values and is a dynamic DOM element
this.doSomethingWithParams(value1); //THROWS TYPEDEF ERROR: this.doSomethingWithParams is not a function
this.getCurrentPosition();
}
};
var local = new customFunctionWrap();
local.doSomething(); //WORKS
I know there is another way to do it and then directly use customFunctionWrap.callWithParams(), but am trying to understand why the former approach is erroring out.
var customFunctionWrap = {
myVar1 : 0,
callWithParams : function(){
}
}
What JS sees:
var customFunctionWrap = (some function)()
returned function is fired, because the last (), so it has to yield/return something, otherwise, like in your code it is "returning" undefined.
So your given code does not work.
The very first fix is to delete last 2 characters from
var customFunctionWrap = (some function)()
to make it return constructor.
I have 3 "dice" objects created from this custom constructor:
function Dice() {
this.value = 0;
this.keep = false;
this.roll = function() {
this.value = Math.floor(Math.random()*6)+1;
};
}
Then, inside function rollOnce(), I have 3 HTML buttons inside a document.getElementById("paragraph1").innerHTML command that will display each dice's value as follows:
function rollOnce() {
(...)
document.getElementById("paragraph1").innerHTML =
'<button id="diceOne" class="unkept" onclick="keepDice(this.id)">'+dice1.value+'</button> ' +
'<button id="diceTwo" class="unkept" onclick="keepDice(this.id)">'+dice2.value+'</button> ' +
'<button id="diceThree" class="unkept" onclick="keepDice(this.id)">'+dice3.value+'</button> ';
}
Now, function keepDice(diceId) will set attribute class="kept" for each dice/button that has been clicked.
The next thing I want to do is to know which dice variable (dice1, dice2, dice3) has been clicked (in order to keep their value by doing diceN.keep = true;. Because after that there will be another round of the game in which only those dice which are "unkept" will get another diceN.roll() call. But my knowledge is still very limited and I only know how to access (HTML only) elements by using document.getElementsBy(...) (this is the HTML DOM, right? I'm currently learning this at W3Schools).
I have not yet learned about jQuery, AngularJS and all the other cool webdev stuff. So if it is possible to answer using only Javascript it would be much appreciated (even if other libs would make it easier! It's a bonus if there are alternative solutions and I would be happy to learn too!). Is this possible at all?
Thanks in advance,
Maybe something like class="kept-'+dice1.keet+'" onclick="keepDice(1)"
then
function keepDice(index){
dices[index].keep = true;
turns--;
if (turns > 0) {
rollOnce()
}
}
Try this:
function keepDice(id) {
var whichDice;
switch(id) {
case 'diceOne':
whichDice = dice1;
break;
case 'diceTwo':
whichDice = dice2;
break;
case 'diceThree':
whichDice = dice3;
break;
}
whichDice.keep = true;
}
If you stored your dice in an associative array like this:
dice['diceOne'] = new Dice();
dice['diceTwo'] = new Dice();
dice['diceThree'] = new Dice();
you would create the buttons almost the same way
<button id="diceOne" class="unkept" onclick="keepDice(this.id)">dice["diceOne"].value</button>
you could then write your dice function like this
function keepDice(id)
{
dice[id].keep = true;
document.GetElementById(id).setAttribute("class","kept");
//...
}
I came back to this again and realised there's a better way. It's quite a different approach than what you've got so far, but let me explain...
I know your question title is "How to get Javascript object from HTML element" but my answer better serves the question "How to get HTML element from Javascript object" and also better solves the problem you're facing.
First, I set the stage by creating a container element #paragraph1 and a "Roll Once" button which runs the rollOnce() function
<p id="paragraph1"></p>
<button onclick="rollOnce()">Roll Once</button>
Then I create the Dice() Object which takes a parameter - this parameter is the id of the element we wish to use as a container. We must wait for the HTML to load before we can find that container because until then, it simply doesn't exist yet. That's why I have bound a function to the document.onreadystatechange event.
So when the HTML has loaded and the document is ready, I initialise the Object, storing it in a var and the Object has all the required functions built-in for managing it's button.
function Dice(container) {
this.button = document.createElement("button");
this.button.innerHTML = 0;
document.getElementById(container).appendChild(this.button);
this.button.addEventListener('click', function() {
this.className = 'kept';
});
this.roll = function() {
if(this.button.className != 'kept') {
this.button.innerHTML = Math.floor(Math.random()*6)+1;
}
}
}
var dice1;
var dice2;
var dice3;
document.onreadystatechange = function () {
if(document.readyState == "complete") {
dice1 = new Dice("paragraph1");
dice2 = new Dice("paragraph1");
dice3 = new Dice("paragraph1");
rollOnce();
}
}
function rollOnce() {
dice1.roll();
dice2.roll();
dice3.roll();
}
Fully working demonstration is here: http://codepen.io/anon/pen/groEmg
Edit: If you want to get the values of the dice later, you can access the Objects' properties like so: dice1.button.innerHTML
You need to keep track of what has been kept and what has not been kept. It would be useful to hold all the dice functionality inside the dice class. every time you run rollOnce() you must also represent the kept/unkept state in the className.
Here's an example including what I gather is your current initialisation - define var dice then define rollOnce() then run rollOnce()
function Dice() {
this.value = 0;
this.kept = false;
this.roll = function() {
if(!this.kept) this.value = Math.floor(Math.random()*6)+1;
};
this.keep = function(id) {
this.kept = true;
document.getElementById(id).className = 'kept';
}
}
var dice1 = new Dice();
var dice2 = new Dice();
var dice3 = new Dice();
function rollOnce() {
dice1.roll();
dice2.roll();
dice3.roll();
document.getElementById("paragraph1").innerHTML =
'<button id="diceOne" class="'+(dice1.kept?'kept':'keep')+'" onclick="dice1.keep(\'diceOne\')">'+dice1.value+'</button> ' +
'<button id="diceTwo" class="'+(dice2.kept?'kept':'keep')+'" onclick="dice2.keep(\'diceTwo\')">'+dice2.value+'</button> ' +
'<button id="diceThree" class="'+(dice3.kept?'kept':'keep')+'" onclick="dice3.keep(\'diceThree\')">'+dice3.value+'</button> ';
}
rollOnce();
I've made it pass an ID to Dice.keep(id) just to have a live update of the DOM element which represents this Object variable.
Some clarification on the classnames since you're a beginner: I used ternary logic operators to quickly perform an IF THEN ELSE
So the part that says dice1.kept?'kept':'keep'
Actually means IF dice1.kept THEN 'kept' ELSE 'keep'
You can put a blank '' instead of 'keep' if you like since I don't think it's being used (but you might use it for CSS). Of course, there is plenty of room for improvement all over this code, but I wanted to keep it as similar to your sample code as possible. In fact, the first thing I would do is probably change the onclick to this: onclick="dice1.keep(this)" and then change your object like:
this.keep = function(button) {
this.kept = true;
button.className = 'kept';
}
http://codepen.io/anon/pen/MyrxyX
Edit: here's a slightly modified version where the Dice() object is agnostic to the DOM but still provides all the relevant data: http://codepen.io/anon/pen/MyrxbB
I'm writing simple slider for my website. This slider contains list items. I want to use OOP approach.
My actual code:
var miniSlider = function(objId)
{
this.obj = $("#" + objId);
this.obj.settings = [];
this.obj.settings['items'] = $('ul li', this.obj).length;
this.pagerNext = this.obj.find("i.next");
this.pagerPrev = this.obj.find("i.prev");
this.pagerNext.on("click", function() {
alert(this.obj.settings['items']);
});
};
I can invoke a few other sliders (yes, that's why I introduced a class):
miniSlider("mini-slider");
The problem is that when I'm in jQuery this.pagerNext.on("click", function() { }); this is no longer my object but - it's become a clicked element. How can I access this.obj.settings after click in a well done way (and with multi sliders support)?
EDIT:
Here is a full code created with a cooperation with SOF community :)
var MiniSlider = function(objId)
{
this.obj = $("#" + objId);
this.obj.settings = {
items: $("ul li", this.obj).length,
autoChangeTime: 8000
};
this.obj.activeElement = null;
this.pagerNext = this.obj.find("i.next");
this.pagerPrev = this.obj.find("i.prev");
var self = this;
this.pagerNext.on("click", function() {
self.obj.activeElement = $('li.active', self.obj);
if(self.obj.settings.items > 0)
{
if(self.obj.activeElement.is(':last-child'))
{
$('li.active', self.obj).removeClass('active');
$('li', self.obj).first().addClass('active');
}
else
{
self.obj.activeElement.next().addClass('active').prev().removeClass('active');
}
}
});
this.pagerPrev.on("click", function()
{
self.obj.activeElement = $('li.active', self.obj);
if(self.obj.settings.items > 0)
{
if(self.obj.activeElement.is(':first-child'))
{
self.obj.activeElement.removeClass('active');
$('li', self.obj).last().addClass('active');
}
else
{
self.obj.activeElement.prev().addClass('active').next().removeClass('active');
}
}
});
this.obj.parent().on('mouseenter mouseleave', function(e) {
if (e.type == 'mouseenter')
{
$(this).addClass('stop');
}
else
{
$(this).removeClass('stop');
}
});
setInterval(function() {
if(self.obj.settings.items > 0 && !self.obj.parent().hasClass("stop"))
{
self.pagerNext.click();
}
}, this.obj.settings.autoChangeTime);
};
and invoke:
new MiniSlider("mini-slider");
Alex gave you the solution to the this problem in your callback, but there is another problem in your code.
You are calling the miniSlider() function without a new operator:
miniSlider("mini-slider");
That means that inside the function, this is not a unique object, but is actually the window object!
You need to use the new operator to create an individual object for each call:
new miniSlider("mini-slider");
But you should also change the name of this function to follow the JavaScript convention that constructors begin with a capital letter. Call it MiniSlider and use it like so:
new MiniSlider("mini-slider");
If you follow this convention (which most experienced JavaScript programmers do), it will help you remember when to use new. If the function begins with a capital letter, it's a constructor and you need to use new with it. Otherwise, you don't.
If you'd like to be able to use your constructor without new, that is also possible with a bit more code, e.g.:
function MiniSlider( objId ) {
if( this == window ) return new MiniSlider( objId );
// the rest of your constructor code goes here
}
But generally people don't bother with that and just use the initial capital letter on the constructor as a reminder to use new.
Also, as a suggestion, I like to use a meaningful name when I save this in a variable, and then I use that name consistently instead of using this at all. Doing it this way it might look like:
var miniSlider = function(objId) {
var slider = this;
slider.obj = $("#" + objId);
slider.obj.settings = [];
slider.obj.settings['items'] = $('ul li', slider.obj).length;
slider.pagerNext = slider.obj.find("i.next");
slider.pagerPrev = slider.obj.find("i.prev");
slider.pagerNext.on("click", function() {
alert(slider.obj.settings['items']);
});
};
Why do I prefer that approach over using this in most places and another variable like self where you need it? This way I don't have to remember which to use: I can always use slider in the code instead of this. (Of course you could use self or any other name; I just like to have a more meaningful name if I'm going to the trouble of making up a name at all.)
Another minor problem in the code is in these two statements:
slider.obj.settings = [];
slider.obj.settings['items'] = $('ul li', slider.obj).length;
You shouldn't use an Array when you are going to be giving it named properties like this. Use an Object instead. Arrays should only be used when you have numeric indexes like 0, 1, 2, etc. And with an object literal you can set the property at the same time:
slider.obj.settings = {
items: $('ul li', slider.obj).length
};
Also, when you use that property:
alert(slider.obj.settings['items']);
you can write it more simply as:
alert(slider.obj.settings.items);
Either way it does the same thing.
Save a reference to this in a local variable, and use that variable instead of this in the nested function.
var self = this;
this.pagerNext.on("click", function() {
alert(self.obj.settings['items']);
});
I have a problem, I want to create a JavaScript class:
function Calculatore(txt,elements) {
this.p= new Processor();
this.output=txt;
$(elements).click(this.clickHandler);
}
Calculatore.prototype.clickHandler = function() {
var element=$(this);
// Code Here
// "this" contains the element.
// But what if I want to get the "output" var?
// I tried with Calculatore.prototype.output but no luck.
}
So how can I solve this?
You can use jQuery's $.proxy:
function Calculatore(txt,elements) {
this.p= new Processor();
this.output=txt;
$(elements).click($.proxy(this.clickHandler, this));
}
Calculatore.prototype.clickHandler = function(event) {
var clickedElement = event.target;
alert(this.output);
}
Edited. Jason brought up a good point in the comments. It's probably better to use event.target which references only the element clicked, rather than elements which may reference an array of objects matching the selection.
You have a collision between this values. You currently don't have access to the instance because this has been set to the element inside a click handler.
You could make a proxy function to pass both the this value (the element) and the instance:
function Calculatore(txt,elements) {
this.p= new Processor();
this.output=txt;
var inst = this; // copy instance, available as 'this' here
$(elements).click(function(e) {
return inst.clickHandler.call(this, e, inst); // call clickHandler with
// 'this' value and 'e'
// passed, and send 'inst'
// (the instance) as well.
// Also return the return
// value
});
}
Calculatore.prototype.clickHandler = function(e, inst) {
var element = $(this);
var output = inst.output;
};