If I have more than 1 instance of the same plugin on the same page how can I separate functionality. eg. in this demo http://jsfiddle.net/3jwAK/, I have a plugin "editor" that appends a link simulating some plugin/widget button, that when clicked will append a line to the textarea.
The problem with it currently is it only targets the last textarea
Code looks like
JS
(function($) {
$.fn.editor = function(options) {
var helpers = {
rand: function() {
return Math.round(Math.random() * 20);
}
};
return this.each(function() {
$this = $(this);
var div = $("<a>", {
text: "Add Random Number",
href: "#",
click: function() {
$this.val( $this.val() + "\n" + helpers.rand() );
}
});
$this.before(div);
});
}
})(jQuery);
HTML
<textarea name="txtArea1" cols="50" rows="6" id="editor1"></textarea>
The problem is the variable $this needs to be a local copy in each run of that function. Put var in front of it.
var $this = $(this);
Updated: http://jsfiddle.net/3jwAK/1/
Related
I am using the following code in a list of links to translate their title according to the language the user is pointing to. If the user is pointing, for example, to the Chinese language icon, the "article-title" gets replaced with its Chinese version. On mouse off, the title goes back to English. This works just fine.
Where I am running into problems: sometimes I need to add another link after the "article-title", on the same line. Whenever I do this, the script panics and flicks the title between blank and default, because it is finding a link, but it's not telling it to change anything.
How do I add exceptions to this script? How do I make it check to see if the link has the class "foo", and if it does, please just ignore it?
$.each($("li"), function(i, elements) {
var links = elements.getElementsByTagName("a");
var article_title = elements.getElementsByClassName("article-title")[0];
$.each(links, function(j, link) {
var previous_title = article_title.innerHTML;
link.addEventListener("mouseover", function() {
$(article_title).fadeTo(200, 0.5, function(){
article_title.innerHTML = link.title;
$(article_title).fadeTo(200, 1, function(){});
});
});
link.addEventListener("mouseout", function() {
$(article_title).fadeTo(300, 0.5, function(){
article_title.innerHTML = previous_title;
$(article_title).fadeTo(300, 1, function(){});
});
});
});
});
jQuery has a built in function called hasClass(className). Simply use that to see if the a element has that particular class.
$.each($("li"), function(i, elements) {
var links = elements.getElementsByTagName("a");
var article_title = elements.getElementsByClassName("article-title")[0];
$.each(links, function(j, link) {
var previous_title = article_title.innerHTML;
if (!$(link).hasClass("foo")) {
link.addEventListener("mouseover", function() {
$(article_title).fadeTo(200, 0.5, function(){
article_title.innerHTML = link.title;
$(article_title).fadeTo(200, 1, function(){});
});
});
link.addEventListener("mouseout", function() {
$(article_title).fadeTo(300, 0.5, function(){
article_title.innerHTML = previous_title;
$(article_title).fadeTo(300, 1, function(){});
});
});
}
});
});
Simply replace the class "foo" in the above example with the actual class name of links you wish to ignore.
I'm trying to achieve as above - I got as far as I could, but the code does not execute at all.
$('.container').on('click', 'form', function() {
var clone = $('form').clone();
clone.find('input').attr('name', 'pick0' + length);
$('form').appendChild(clone);
});
html + js example on codepen
https://codepen.io/anon/pen/KQrEMd
Edit:
Trying to copy a form based on a click of the form. 1 click = 1 extra form.
Try using this code:
$('.container').on('click', 'form', function() {
var clone = $('form:last').clone();
length = $('form').length;
clone.find('input').attr('name', 'pick' + length);
$('.container').append(clone);
});
try
$('.container').on('click', 'form', function() {
if(typeof($('form').data('length')=='undefined')){
var $length = 1;
}else{
var $length = parseInt($('form').data('length'));
}
var clone = $('form').clone();
clone.find('input').attr('name', 'pick0' + $length);
$('form').append(clone.html());
$('form').data('length',$length+1);
});
});
If i understand your question correctly perhaps this can help
$('.container').on('submit', 'form', function(e) {
e.preventDefault();
console.log(e.target.pick0.value);
var input = e.target.pick0.cloneNode(true);
document.getElementsByClassName('container')[0].appendChild(input);
});
Here is my HTML code:
<textarea id="uno-1" name="1"></textarea>
<textarea id="uno-2" name="2"></textarea>
And here is my JS code:
$("[id^=uno]").keyup(function() {
clearTimeout(typingTimer);
if ($("[id^=uno]").val) {
typingTimer = setTimeout(function() {
$.get("/someUrl", {
text: $("[id^=uno]").val(),
id: $("[id^=uno]").attr('name')
});
}, 1000);
}
});
The point is that text is saved automatically by sending it to GET Url right after typing it. However the problem is that it always catches only the first text area even if I type text in the second text area. I need to send GET attributes respectively. Is there a way to accomplish this?
because you are always referencing all the inputs and you are not using the current only.
$("[id^=uno]").keyup(function() {
clearTimeout(typingTimer);
var input = $(this); //reference the current textarea
if (input.val().length) { //not sure what your check was, but it was strange
typingTimer = setTimeout(function() {
$.get("/someUrl", {
text: input.val(), //use the variable we set about
id: input.attr('name')
});
}, 1000);
}
});
Firstly, use a class to group your elements:
<textarea id="uno-1" class="uno" name="1"></textarea>
<textarea id="uno-2" class="uno" name="2"></textarea>
Then you can use that class along with the this keyword in the event handler to refer to the element which raised the event.
var typingTimer;
$(".uno").keyup(function() {
clearTimeout(typingTimer);
if (this.value) {
typingTimer = setTimeout(function() {
$.get("/someUrl", {
text: this.value,
id: this.name
});
}, 1000);
}
});
Your issue is your use of $("[id^=uno]").val() and $("[id^=uno]").attr('name').
$("[id^=uno]") selects both fields.
.val() then returns the value from the first field.
To fix this use $(this) within the callback.
setTimeout will create a new function scope as well, so be sure to capture the value of this from keyup(function () { using something along the lines of:
self = this;
//or
$this = $(this);
You should use $(this)
$("[id^=uno]").keyup(function() {
clearTimeout(typingTimer);
if ($(this).val) {
typingTimer = setTimeout(function() {
$.get("/someUrl", {
text: $(this).val(),
id: $(this).attr('name')
});
}, 1000);
}
});
I'm making a sidebar with sliding, accordion-like areas, so that when you click on a heading the associated content toggles visibility, as per the portion of code below. The initAccordion function also appends an svg "+" to the headings to indicate their action.
The final task is getting the svg held in the cross variable to rotate by 45 degrees on each click - which is where I'm having trouble.
Obviously, the two statements in the makeButtons function aren't accessible outside their containing function. I've tried to re-factor the code, but it ends up being a jumbled mess and I can't help thinking there must be a simple solution.
toggleContent : function(){
this.toggleClass('on').next().slideToggle(200);
cross.transform("r45");
},
makeButtons : function(el) {
var btn = Raphael(el,15,15);
var cross = btn.path(".....");
},
initAccoridon : function(){
$('#eventSideBar').find('h3').each(function(){
var btn = $('<div/>', {
class : 'sideBarBtn'
});
btn.appendTo(this);
var btnContainer = btn.get(0),
$this = $(this);
sidebar.makeButtons(btnContainer);
$this.on('click', function(){
sidebar.toggleContent.call($this);
});
});
}
I'm not sure if this is better suited to the code-review section of SO - apologies if it is; I can move it on request.
Fiddle here...
Edit: I've managed to get part of the way there, although with multiple elements, I can't get the corresponding svg to rotate; just the last one. Fiddle here, and updated code:
makeSvg : function(el) {
this.btn = Raphael(el,15,15);
this.cross = this.btn.path(".....");
return {
btn : this.btn,
cross : this.cross
};
},
toggleContent : function(){
if (this.hasClass('on')) {
sidebar.cross.transform("r0");
} else {
sidebar.cross.transform("r45");
};
this.toggleClass('on');
},
initAccordion : function(){
$('body').find('h3').each(function(){
var btn = $('<div/>', {
class : 'sideBarBtn'
});
btn.appendTo(this);
var btnContainer = btn.get(0),
$this = $(this);
sidebar.makeSvg(btnContainer);
$this.on('click', function(){
sidebar.toggleContent.call($this);
});
});
}
You will need to pass a parameter of the relevant element or capture it somehow. This example should work for multiple objects, you'll just need to add the reverse action.
jsfiddle
Relevant bit...
var sidebar = {
makeSvg : function(el) {
var btn = Raphael(el,15,15);
return btn.path(pathString).attr({fill:"#fff",stroke:"none"});
},
toggleContent : function( myCross ){
this.toggleClass('on');
myCross.transform("r45");
},
initAccordion : function(){
$('body').find('h3').each(function(){
var btn = $('<div/>', {
class : 'sideBarBtn'
});
btn.appendTo(this);
var btnContainer = btn.get(0),
$this = $(this);
var myCross = sidebar.makeSvg(btnContainer);
$this.on('click', function(){
sidebar.toggleContent.call($this, myCross);
});
});
}
};
I have 2 textfields with id's source,destination. If any field value changes that corresponding model attribute will be change. I did this one using Backbone.Model and events object in Marionette.CompositeView. It's working fine.
Once any model Attribute change corresponding function will call. For this I written the following code. It's not working the problem was even one attribute changes both functions are evaluating.
model Code:
var mapModel = Backbone.Model.extend({
defaults: {
startPlace: "",
endPlace: ""
}
});
Marionette.CompositeView code:
var mapView = Marionette.CompositeView.extend({
events: {
"blur #source": "sAttributeSetting",
"blur #destination": "dAttributeSetting"
},
dAttributeSetting: function() {
this.model.set({"endPlace": document.getElementById(this.ui.destinationPlace).value});
},
sAttributeSetting: function() {
this.model.set({"startPlace": document.getElementById(this.ui.sourcePlace).value});
},
modelEvents: {
"change startPlace": "startMarkerDisplay",
"change endPlace": "endingMarkerDisplay"
},
startMarkerDisplay: function() {
alert("start");
},
endingMarkerDisplay: function() {
alert("end");
}
});
html code:
<input type="text" id="source">
<input type="text" id="destination">
creating instance for both model and view
mapModelObj = new mapModel();
var mapViewObj = new mapView({el:$('#mapDiv'), model:mapModelObj});
problems:
Initially If I enter any value in first field(source) getting 2 alert boxes("start", "end").
Initially If you enter any value in second field(destination) getting 4 alert boxes("start", "end", "start", "end")
I tried alot but I didn't understand where I am getting the problem
Can anyone help me.
Thanks
modelEvents should be connected by :. Say, event of changing startPlace should be
'change:startPlace'
If you use space you'll end with two events, not one event specific to this attribute.
Your code 'change startPlace' represents two events, one is 'change', the other is 'startPlace'. So you'll see "start","end","start","end"
My observations are the following for your solution (however I propose a second solution at the bottom):
The binding of entity event has colon syntax. It should be a hash of { "event:name": "eventHandler" } configuration. Multiple handlers can be separated by a space. A function can be supplied instead of a string handler name.
You can use advantage of the el property of the backbone view.
Instead of using document.getElementById(this.ui.sourcePlace), you can use this.$('#source'). This latest searches only in the context of el rather than searching the whole dom. This way the evaluation will be way faster... That way you should use this expression: this.$('.destination').val()
Please check my jsfiddle about your issue: http://jsfiddle.net/orbanbotond/VEcK6/
The code is the following:
var mapModel = Backbone.Model.extend({
defaults: {
startPlace: "",
endPlace: ""
}
});
var mapView = Marionette.CompositeView.extend({
events: {
"blur .source": "sAttributeSetting",
"blur .destination": "dAttributeSetting"
},
dAttributeSetting: function(){
console.log('end blured');
console.log('input value:' + this.$('.destination').val());
this.model.set({
"endPlace": this.$('.destination').val()
});
console.log('endplace set to: ' + this.model.get('endPlace'));
},
sAttributeSetting: function() {
console.log('start blured');
console.log('input value:' + this.$('.source').val());
this.model.set({
"startPlace": this.$('.source').val()
});
console.log('startPlace set to: ' + this.model.get('startPlace'));
},
modelEvents: {
"change:startPlace": "startMarkerDisplay",
"change:endPlace": "endingMarkerDisplay"
},
startMarkerDisplay: function () {
alert("start");
},
endingMarkerDisplay: function () {
alert("end");
}
});
$(document).ready(function(){
var mapModelObj = new mapModel();
var mapViewObj = new mapView({
el: $('#mapDiv'),
model: mapModelObj
});
});
My proposed second solution:
Use the stickit library which does all you are doing. You only need to define the mapping between the dom selector and the observed model attribute.
Here is the jsfiddle for it: http://jsfiddle.net/orbanbotond/fm64P/
Here is the code:
var mapModel = Backbone.Model.extend({
defaults: {
startPlace: "initialStartPlace",
endPlace: "initialEndplace"
},
});
var mapView = Marionette.CompositeView.extend({
template: "#mapDiv",
events: {
"blur .source": "sAttributeSetting",
"blur .destination": "dAttributeSetting"
},
bindings: {
'.source': {
observe: 'startPlace'
},
'.destination': {
observe: 'endPlace'
}
},
onRender: function() {
this.stickit();
console.debug("Sticked to it already");
},
});
$(document).ready(function(){
var mapModelObj = new mapModel();
var mapViewObj = new mapView({
el: $('#mapDiv'),
model: mapModelObj
});
mapViewObj.render();
mapModelObj.bind('change:startPlace', function(obj){alert("New value: " + obj.get('startPlace'));});
mapModelObj.bind('change:endPlace', function(){alert("New value: " + obj.get('endPlace'));});
});
For every code sample I used this template (I used class selectors instead of id selectors):
<div id="mapDiv">
<input type="text" class="source">
<input type="text" class="destination">
</div>