Class isn't added in angular - javascript

I have an angular function
$scope.show = function(el){
if($scope.steps[el] == true){
$scope.steps[el] = false;
}
else{
$scope.steps = [];
$scope.steps[el] = true;
}
}
When I call It by click this
<span class="" ng-click="show('getDate')">text</span>
Then a class 'shown' adds in this div
<div class="form-block steps second" ng-class="{shown : steps.getDate}">
but I don't get the class when call the fanction in this cod
$(document).on('click', "li", function() {
$scope.show('getDate');
console.log($scope.steps);
});
but in console i get this log
[getDate: true]
LI tag generated with JS by jquery.formstyler http://dimox.name/jquery-form-styler/ from SELECT tag

As #charlietfl stated this is happening due the fact that you updated your DOM outside of the "Angular" world. Thus, resulting of angular not knowing you did such a change.
In order to fix it you should force angular to digest by using the keyword $apply.
Example:
$(document).on('click', "li", function() {
$scope.$apply(function () {
$scope.show('getDate');
});
console.log($scope.steps);
});
Important Note: Most of the time It's poor behavior to use jQuery rather than the AngularJS way.
Thanks for #Bhojendra Nepal for noticing the bug.

Related

jQuery nested functions

I am still new to JavaScript and jQuery, so I am confused as to why the following code is not working as I anticipated. All I am trying to do is save input on a button click (id=recordInput) and display it with another button click (id=displayInput). What I observe is that tempInput is stored, (the code works until that point) but assignment of displayInputs onclick attribute is not executed. My question is, can you not nest a $().click() call inside of another &().click() call?
<script>
$(document).ready(function () {
$('#recordInput').click(function(event) {
var tempInput = $('#testInput').val();
&('#displayInput').click(function(event) {
console.log(tempInput);
});
});
});
</script>
My thinking is this in pseudocode:
assign recordInput onclick attribute to the following function:
store tempInput
set displayInput onclick to alert the tempInput value
what is wrong with my thinking?
NOTE: I did not include any html tags but all of the ids are referenced correctly
It's not working because you have put & instead of $ here
$('#displayInput').click(function(event) {
Fixing this may work, but you shouldn't set event handlers this way. Because every time your first handler function is called it will set an event handler for the second one. You can try with your console.log and you will see that the number of console.log is increasing by every click on #recordInput. So you should better set it like this :
var tempInput;
$('#recordInput').click(function(event) {
tempInput = $('#testInput').val();
});
$('#displayInput').click(function(event) {
console.log(tempInput);
});
I would change
$(document).ready(function () {
$('#recordInput').click(function(event) {
var tempInput = $('#testInput').val();
&('#displayInput').click(function(event) {
console.log(tempInput);
});
});
});
to
$(function(){
var testInput = '';
$('#recordInput').click(function(){
testInput = $('#testInput').val();
});
$('#displayInput').click(function(){
if(testInput !== ''){
console.log(testInput);
}
});
});
You are using & instead of $. Of course, you don't have to format the code exactly like I did.

Why would this JS fire on a form partial rendered on 1 page but not another?

I have a posts.js file that looks like this:
var ready;
ready = function() {
var toggleSidebar = $(".togglesidebar");
var primary = $("#primary");
var secondary = $("#secondary");
toggleSidebar.on("click", function(){
if(primary.hasClass("col-sm-9")){
primary.removeClass("col-sm-9");
primary.addClass("col-sm-12");
secondary.css('display', 'none');
}
else {
primary.removeClass("col-sm-12");
primary.addClass("col-sm-9");
secondary.css('display', 'inline-block');
}
});
};
var counter = function(event) {
var fieldValue = $(this).val();
var wc = fieldValue.trim().replace(regex, ' ').split(' ').length;
var regex = /\s+/gi;
var $wcField;
var maxWc;
if ($(this)[0] === $('#post_title')[0]) {
$wcField = $('#wordCountTitle');
maxWc = 7;
} else {
$wcField = $('#wordCountBody');
maxWc = 150;
}
$wcField.html(wc);
$wcField.toggleClass('over-limit', wc > maxWc);
};
$(document).ready(ready);
$(document).on('ready page:load', function () {
$('#post_title, #body-field').on('change keyup paste', counter);
});
In my application.html.erb page, I have this:
<div id="secondary" class="col-sm-3 sidebar" style="display: none;">
<aside>
<div class="about">
<h4>Submit Report</h4>
<%= render "posts/form" %>
</div>
</aside>
</div> <!-- /#secondary -->
And when I toggle this div, and the _form partial is displayed, the JS works fine in those fields.
But if I go to posts/new or /posts/:id/edit, it doesn't.
Even though if I check the source of that page, I see the post.js included there.
What could be causing this?
Edit 1
I am using Turbolinks, if that matters.
Edit 2
I tried this, per suggestions in the Answers:
var ready;
ready = function() {
// This is the Sidebar toggle functionality
var toggleSidebar = $(".togglesidebar");
var primary = $("#primary");
var secondary = $("#secondary");
$(document).on("click", ".togglesidebar", function(){
if(primary.hasClass("col-sm-9")){
primary.removeClass("col-sm-9");
primary.addClass("col-sm-12");
secondary.css('display', 'none');
}
else {
primary.removeClass("col-sm-12");
primary.addClass("col-sm-9");
secondary.css('display', 'inline-block');
}
});
};
But that hasn't worked.
The key issue I am having a problem with is the 'counters', i.e. #wordCountTitle and #wordCountBody don't work on my /posts/new even though they work on the posts/index when .toggleSidebar is activated.
Instead of binding directly to the element's onclick which needs careful handling of Turbolinks events, you can use an event handler on the document, try changing the direct event
toggleSidebar.on("click", function(){
to the delegated event
$(document).on("click", ".togglesidebar", function(){
When you modify the DOM dynamically (as when Turbolinks replaces it) if you use a direct event then you would need to re-assign it.
For a detailed explanation see http://api.jquery.com/on/#direct-and-delegated-events
The same that goes for the first function stands for the second. Also, with delegated events the "ready" check becomes unnecessary. With this in mind, your code would become:
$(document).on("click", ".togglesidebar", function(){
var primary = $("#primary");
var secondary = $("#secondary");
if(primary.hasClass("col-sm-9")){
primary.removeClass("col-sm-9");
primary.addClass("col-sm-12");
secondary.css('display', 'none');
}
else {
primary.removeClass("col-sm-12");
primary.addClass("col-sm-9");
secondary.css('display', 'inline-block');
}
});
$(document).on('change keyup paste', '#post_title, #body-field', function () {
var fieldValue = $(this).val();
var wc = fieldValue.trim().replace(regex, ' ').split(' ').length;
var regex = /\s+/gi;
var $wcField;
var maxWc;
if ($(this)[0] === $('#post_title')[0]) {
$wcField = $('#wordCountTitle');
maxWc = 7;
} else {
$wcField = $('#wordCountBody');
maxWc = 150;
}
$wcField.html(wc);
$wcField.toggleClass('over-limit', wc > maxWc);
});
I am using something like this on one of my projects, had the same problem hope it helps:
window.onLoad = function(callback) {
$(document).ready(callback);
$(document).on('page:load', callback);
};
and then wrap up my functions with onLoad, something like
onLoad(function() {
counter()
});
The onLoad function binds the ready event and the turbolink page:load event
when you do
$("selector").on("click", function(){});
you actually bind the selector to the click event.
White if you use
$(document).on("click", "selector", function(){});
You bind the click event to the document, which after the click checks if the clicked element was the selector you used. if yes, it executes the function. So you should use the second approach whenever binding events on dynamic elements.
I hope that answers the question of "why"
Long time I don't work with jQuery, but since I got here: If you have more than one element with the selector ".sidebar", I believe you'll need to use ".each" to bind the function to all elements that match that selector on the dom.
For the reference go here http://api.jquery.com/each/
Hope this helps, good luck.
I took a look on Turbolinks, and it does what I thought it did: It loads the HTML content of a link inside a container on the main page. Problem is, anything it loads is agnostic of whatever events and functions you have declared when the main page loaded, so indeed, after the first click, the selector on the HTML it has just loaded won't have the click event attributed to it (it was not on the DOM when you did the binding).
Possible solution: I did a little research on the .live() method, but is has been deprecated, so I recommend doing something like this:
$('body').on('click','a.saveButton', saveHandler)
Binding closer to the element you need will, it seems, will assure that whatever Turbolinks loads inside the body will get the bindings you have declared.
There is a more detailed answer here: Javascript event binding persistence
The documentation for the .live hook is here: http://api.jquery.com/live/#typefn
I used to have the same architecture on my web pages back in the day, and I did run on a problem similar to yours.
Hope it helps.
Should it work.. make sure:
Nothing id conflict.
Your html structure, maybe you forgot put value in your post.
Maybe wrong path posts.js, open with CTRL+U and then click post.js what is showing your code, if there so it's ok.
I try like this, it's ok(dummy):
$(document).on("click", ".togglesidebar", function(){
var primary = $("#primary");
var secondary = $("#secondary");
if(primary.hasClass("col-sm-9")){
primary.removeClass("col-sm-9");
primary.addClass("col-sm-12");
secondary.css('display', 'none');
}
else {
primary.removeClass("col-sm-12");
primary.addClass("col-sm-9");
secondary.css('display', 'inline-block');
}
});
$(document).on('change keyup paste', '#post_title, #body-field', function () {
var fieldValue = $(this).val();
var wc = fieldValue.trim().replace(regex, ' ').split(' ').length;
console.log(wc);
var regex = /\s+/gi;
var $wcField;
var maxWc;
if ($(this)[0] === $('#post_title')[0]) {
$wcField = $('#wordCountTitle');
maxWc = 7;
} else {
$wcField = $('#wordCountBody');
maxWc = 150;
}
$wcField.html(wc);
$wcField.toggleClass('over-limit', wc > maxWc);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="togglesidebar">Toogle</div>
<div id="wordCountTitle"></div>
<div id="wordCountBody"></div>
<div id="primary">
<div id="secondary" class="col-sm-3 sidebar" style="display: none;">
<aside>
<div class="about">
<h4>Submit Report</h4>
<input type="text" id="body-field"/>
</div>
</aside>
</div> <!-- /#secondary -->
</div>
Honestly with Js problems involving Turbolinks the best way to have it work efficiently is to install jquery rails gem, add // require jquery-Turbolinks and then remove //require turbolinks in your Js file
Make sure that the ids you are passing on the jQuery selectors are unique. If that page/partial is loaded without a postback, you should use
//document or selector that does not get removed
var primary = $(document).find('#primary');
var secondary = $(document).find('#secondary');
this, with
$(document).click('eventName', 'contextSelector', function)
should help resolve the issue.
If #post_title and #body-field are created dynamically you'll need to change:
$('#post_title, #body-field').on('change keyup paste', counter);
To this:
$(document).on('change keyup paste', '#post_title, #body-field', counter);
You'll need to delegate your events to elements that exist on page load (the document itself, in this case) when targeting elements that don't exist when the page is loaded (probably #post_title and #body-field).
Regarding $('#wordCountTitle') and $('#wordCountBody') on /posts/new, have you tried just typing in either of them at the console? It's possible that the view for /posts/new is different to your index and is missing those ids (or you made them classes or some other transposition happened).
I was having all kinds of issues with Turbolinks and jquery so a few suggestions on how I would go about trying to fix your problems:
Use gem 'turbolinks' to include it. Follow the instructions and do it the Ruby (gems) way rather than the PHP way.
Using Turbolinks means that$(document).ready doesn't fire when a new 'page' loads. The solution is to use: $(document).on('page:load', ready) along with $(document).ready(ready). The page:load event is the Turbolinks version of the ready event.
Others have suggested it but I've found it incredibly valuable in my rails app: Rather than binding events directly to selectors $("a").click(/* function */), binding to the document means that when Turbolinks loads a new page, the document binding survives the page load: $(document).on('click', 'a.particularAnchor', particularAnchorClicked)
With your specific code in mind, I would change this:
$('#post_title, #body-field').on('change keyup paste', counter);
To
$(document).on('change keyup paste', '#post_title, #body-field', counter);
It also seems to me that in your counter function you have mixed the following two lines up (regex needs to be defined first)
var wc = fieldValue.trim().replace(regex, ' ').split(' ').length;
var regex = /\s+/gi;

Click events from two scripts on same element?

Edit: I think I got the solution! I want to try and fix this myself before I ask for further help = )
First script inhibits the second one from functioning as the click event from the first one overides the second one. Because the second one does not function it is impossible to open the drop down menu to select a list item to trigger the first scripts click.
What I tried was replacing all return false statements with event.stopPropagation(). Didnt work however. Tried re-ordering my scripts but that failed as well. I was thinking of making my second script target another parent div but that didnt work either.I also tried event.stopImmediatePropagation() and .bind methods.
Any idea?
First script that makes the drop down function. Contains click event.
function DropDown(el) {
this.f = el;
this.placeholder = this.f.children('span');
this.opts = this.f.find('ul.dropdown > li');
this.val = '';
this.index = -1;
this.initEvents();
}
DropDown.prototype = {
initEvents : function() {
var obj = this;
obj.f.on('click', function(event){
$(this).toggleClass('active');
return false;
});
obj.opts.on('click',function(){
var opt = $(this);
obj.val = opt.text();
obj.index = opt.index();
obj.placeholder.text(obj.val);
});
},
getValue : function() {
return this.val;
},
getIndex : function() {
return this.index;
}
}
$(function() {
var f = new DropDown( $('#f') );
$(document).click(function() {
// all dropdowns
$('.filter-buttons').removeClass('active');
});
});
Second script that does the filtering, also contains click event:
jQuery(document).ready(function(e) {
var t = $(".filter-container");
t.imagesLoaded(function() {
t.isotope({
itemSelector: "figure",
filter: "*",
resizable: false,
animationEngine: "jquery"
})
});
$(".filter-buttons a").click(function(evt) {
var n = $(this).parents(".filter-buttons");
n.find(".selected").removeClass("selected");
$(this).addClass("selected");
var r = $(this).attr("data-filter");
t.isotope({
filter: r
});
evt.preventDefault();
});
$(window).resize(function() {
var n = $(window).width();
t.isotope("reLayout")
}).trigger("resize")
});
html structure
<div id="f" class="filter-buttons" tabindex="1">
<span>Choose Genre</span>
<ul class="dropdown">
<li>All</li>
<li>Electronic</li>
<li>Popular</a></li>
</ul>
</div>
This doesn't really solve your problem but I was bored while drinking my coffee and felt like helping you write your dropdown plugin a little nicer
My comments below are inline with code. For uninterrupted code, see DropDown complete paste.
We start with your standard jQuery wrapper (function($){ ... })(jQuery)
(function($) {
// dropdown constructor
function DropDown($elem) {
First we'll make some private vars to store information. By using this.foo = ... we expose things (probably) unnecessarily. If you need access to these vars, you can always create functions to read them. This is much better encapsulation imo.
// private vars
var $placeholder = $elem.children("span");
var $opts = $elem.find("ul.dropdown > li")
var value = "";
var index = -1;
Now we'll define our event listeners and functions those event listeners might depend on. What's nice here is that these functions don't have to access everything via this.* or as you were writing obj.f.* etc.
// private functions
function onParentClick(event) {
$elem.toggleClass("active");
event.preventDefault();
}
function onChildClick(event) {
setValue($(this));
event.preventDefault();
}
function setValue($opt) {
value = $opt.text();
index = $opt.index();
$placeholder.text(value);
}
Here's some property descriptors to read the index and value
// properties for reading .index and .value
Object.defineProperty(this, "value", {
get: function() { return value; }
});
Object.defineProperty(this, "index", {
get: function() { return index; }
});
Lastly, let's track each instance of DropDown in an array so that the user doesn't have to define a special listener to deactivate each
// track each instance of
DropDown._instances.push(this);
}
This is the array we'll use to track instances
// store all instances in array
DropDown._instances = [];
This event listener deactivate each "registered" instance of DropDown
// deactivate all
DropDown.deactiveAll = function deactiveAll(event) {
$.each(DropDown._instances, function(idx, $elem) {
$elem.removeClass("active");
});
}
Here's the document listener defined right in the plugin! The user no longer has to set this up
// listener to deactiveAll dropdowns
$(document).click(DropDown.deactiveAll);
Might as well make it a jQuery plugin since everything in our DropDown constructor relies upon jQuery. This let's the user do var x = $("foo").dropdown();
// jQuery plugin
$.fn.dropdown = function dropdown() {
return new DropDown($(this));
};
Close the wrapper
})(jQuery);
Now here's how you use it
$(function() {
var x = $('#f').dropdown();
// get the value
f.value;
// get the index
f.index;
});
Anyway, yeah I know this doesn't really help you with your click listeners, but I hope this is still useful information to you. Off to the Post Office now!
I think you're going to need to simplify this to figure out what's going on. There's actually not enough information to see what elements the events are being attached to here.
For argument's sake, open the console and try the following:
$(document).on('click', function() { console.log('first'); return false; });
$(document).on('click', function() { console.log('second'); return false; });
Then click in the page. You'll see that both events are triggered. It might well be that your code is actually attaching the events to different elements (you don't say anywhere). If that's the case then you need to understand how event bubbling works in the DOM.
When you trigger an event, say a click on an element, that event will fire on that element, and then on it's parent, then grandparent etc all the way to the root node at the top.
You can change this behaviour by calling functions in the event itself. evt.stopPropagation tells the event to not bubble up to the ancestor nodes. evt.preventDefault tells the browser not to carry out the default behaviour for a node (eg, moving to the page specified in the href for an A tag).
In jQuery, return false from an event handler is a shortcut for, evt.preventDefault and evt.stopPropagation. So that will stop the event dead in its tracks.
I imagine you have something like:
<div event_two_on_here>
<a event_one_on_here>
</div>
If the thing that handles event_one_on_here calls stopPropagation then event_two_on_here will never even know it has happened. Calling stopPropagation explicitly, or implicitly (return false) will kill the event before it travels to the parent node/event handler.
UPDATE: In your case the issue is that the handler on .filter-buttons a is stopping the propagation (so #f doesn't get to run its handler).
$(".filter-buttons a").click(function(evt) {
// your code here...
// Don't do this - it stops the event from bubbling up to the #f div
// return false;
// instead, you'll probably just want to prevent the browser default
// behaviour so it doesn't jump to the top of the page ('url/#')
evt.preventDefault();
});

need the script to be called for "onload" too not just on "blur"

the code is:
$(document).ready(function () {
$('#url0, #url1, #url2, #url3, #url4, #url5, #url6, #url7, #url8, #url9, #url10').each(function(index, element) {
$(element).blur(function() {
var vals = this.value.split(/\s+/);
var $container = $(this).hide().prev().show().empty();
$.each(vals, function(i, val) {
if (i > 0) {
$("<span> </span>").appendTo($container);
}
$("<a />")
.html(val)
.attr('href',/^https?:\/\//.test(val) ? val : 'http://' + val)
.appendTo($container)
.click(handleClickEvent);
});
});
}).trigger('blur');
// ms to wait for a doubleclick
var doubleClickThreshold = 300;
// timeout container
var clickTimeout;
$('.aCustomDiv a').click(handleClickEvent);
$('.aCustomDiv').click(handleDivClick);
function handleClickEvent(e) {
var that = this;
var event;
if (clickTimeout) {
try {
clearTimeout(clickTimeout);
} catch(x) {};
clickTimeout = null;
handleDoubleClick.call(that, e);
return false;
}
clickTimeout = setTimeout(function() {
clickTimeout = null;
handleClick.call(that, event);
}, doubleClickThreshold);
return false;
}
function handleDivClick(e) {
var $this = $(this);
$this.parent()
.find('input,textarea')
.show()
.focus();
$this.hide();
}
function handleDoubleClick(e) {
var $this = $(this).parent();
$this.parent()
.find('input,textarea')
//.val($a.text())
.show()
.focus();
$this.hide();
}
function handleClick(e) {
window.open(this.href, '_blank')
}
});
HTML CODE:
<div style="padding:0 !important;margin-top:8px !important;">
<div class="aCustomDiv" style="padding: 0px ! important; display: block;">
www.google.com<span></span>www.facebook.com<span></span>www.wikipedia.org
</div>
<input type="text" value="www.google.com www.facebook.com www.wikipedia.org" onchange="immediateEditItemInCart(this.value,'url',0,'pr','35')" class="mandatory0" id="url0" style="display: none;" readonly="readonly">
this script does the following:
converts the text to url for those ids (url0 ...)
double click on the link makes it editable
one click on the div area, next to link makes it editable
one click on the link => goes to the page
my problem : for some reason i don't know, the one click on the link doesn't go to the page but edits it, only the FIRST time , after that works great, so i want the first function to be called also onload not only when blur. how can i do this ?
As far as loading on startup, javascript is single threaded so just firing a method will work if you keep things in order (... just add a couple of parens). But because you are trying to access the DOM you do want to wait for the elements to be available (otherwise you will get nothing back from a selector).
But you do already have:
$(document).ready( function(){} );
Which does exactly what you are asking for, it also has a shorthand of:
$( function(){} );
So I would have to agree with rodneyrehm that is it probably some collision that you have with other js on your page. You might want to encapsulate it a bit in some namespace to make sure that is the not the problem.
I wrote up a quick version that should get your on your way as a starting point if you are still having problems: http://jsfiddle.net/scispear/dUWwB/. I pulled the 'updateURL ' method out in case your ajax call (you mentioned) was just pre-populating the field (that way you could pass in the value also).
It also works with multiple inputs/displays which is what I think you were going for was not 100% sure.

edit in place with jQuery selecting newly inserted elements

I am trying to write my own edit-in-place using jQuery. My code is something like this;
$(".comment-edit").bind({
click: function(){
commentId = $(this).parent().parent().attr("id");
commentEditable = $("#"+commentId+" .comment-text");
if (commentEditable.hasClass('active-inline')) {
alert("already editable aq");
return false;
}
contents = $.trim($("#"+commentId+" .comment-text").text());
commentEditable.addClass("active-inline").empty();
var editBox = '<textarea id="newComment"cols="50" rows="6"></textarea><button class="newCommentSave">Save</button><button class="newCommentCancel">Cansel</button>';
$(editBox+" textarea").val(contents).appendTo(commentEditable).focus();
$.(".newCommentSave").live({
click: function(){
alert("Save");
return false;
}
});
$.(".newCommentCancel").click(function(){
alert("Cancel");
return false;
});
return false;
}
});
As you can see I tried both "live()" and "click()" for interacting with the newly created buttons. However this doesn't work.
I am getting XML filter is applied to non-XML value (function (a, b) {return new (c.fn.init)(a, b);})
Any ideas? What seems to be going wrong?
Edit:
Html looks something like this:
<div class="comment" id="comment-48">
<div class="comment-author">
defiant
<span class="date">2010-11-09 01:51:09</span>
</div>
<div class="comment-text">Comment Text....</div>
</div>
The problem is here:
var editBox = '<textarea id="newComment"cols="50" rows="6"></textarea><button class="newCommentSave">Save</button><button class="newCommentCancel">Cancel</button>';
$(editBox+" textarea").val(contents).appendTo(commentEditable).focus();
editBox is a string, so you're getting this as a result:
$("<textarea/><button /> textarea")
...which isn't XML or a valid selector, throwing an error. Instead you want this:
$(editBox).filter("textarea").val(contents)
.end().appendTo(commentEditable).focus();
This gets the <textarea> from that object you just created via .filter() (since it's a root level element), sets the contents, then uses .end() to hop back in the chain to $(editBox) which contains both elements to append. This would focus the button though, so you may want this instead:
$(editBox).appendTo(commentEditable).filter("textarea").val(contents).focus();
As it turns out, the reason for XML error was a "."
$.(".newCommentSave").live({
// stuff
})
The dot after the dollar sign is what causing this error. At least the code was working fine without it.
I tend to do something like this to attach a click event (to a span in my example)
var span = $("<span>some text</span>");
span.click( function() { alert('yay'); });
I'd break down your editBox variable into three different variables and see what happens then.
The .live() syntax is .live('event', function), I don't think it accepts a map of event:function pairs.
So would something like this work?
$.(".newCommentSave").live('click', function(){
alert("Save");
return false;
});
I'm not sure why your .click() handler didn't work though.

Categories

Resources