Changing observable array alters visibility of custom binding handler, knockoutjs - javascript

Update: JSFiddle http://jsfiddle.net/OrganicCat/CjH87/6/
I have an area that is populated by a normal observable array, and when a button is clicked, an asynchronous service call is made that repopulates that array, but with more data.
This also causes a hidden dom element to display and displays some of that array data there.
Everything works fine until the observable array data is updated, it closes the hidden dom element. It is this event inside Knockout.js(library) that triggers it:
// Ignore writes if the value hasn't changed
if ((!observable['equalityComparer']) || !observable['equalityComparer'](_latestValue, arguments[0])) {
observable.valueWillMutate();
_latestValue = arguments[0];
if (DEBUG) observable._latestValue = _latestValue;
observable.valueHasMutated(); // This event HERE
Is there a way to prevent this from closing the custom binding? Could anything in the binding be causing this? Here is my custom binding handler:
ko.bindingHandlers.expandAmenities = {
init: function (element) {
$('.expandable.closed').hide();
$('.itineraryRowMain .t-go .toggle-expand-rowAmenities').unbind('click').on('click', function (e) {
var $itin_body = $(this).closest('.module-admin-group');
if ($itin_body.hasClass('closed')) {
$(this).parent().parent().next().show();
self.bindAmenities(); // Bind amenity details on open
//$(this).children().html('-');
} else {
$(this).parent().parent().next().hide();
//$(this).children().html('+');
}
$itin_body.toggleClass('open closed');
});
}
};
To summarize, the expand area has a clickable element that will show more data. When this updates the array (just a plain old self.listofStuff(arr);) this causes the new area to get hidden again.

I've figured out the answer. So the problem is that if you bind an array that generates DOM elements (like a list or whatever) and you have elements inside that array that you want to update without doing a .push and without resetting the entire array with self.myArray(newArray) then you have to use an observable variable within the array and update that.
When you modify the observable, it will not redraw the entire array, thereby keeping your dynamically altered elements (like visible/hidden divs within the DOM array) in the same state if they were altered by jQuery or whatever.

Related

How do you store new value in a variable with an existing value based on a click?

I am making a map using leaflet where on click, the values stored in the geojson properties populate textareas in a sidebar. I will use these values to construct an URL that will hit an API. My intention is that the user can click any three box where class = "selected" and get back info on the map based his or her selection. But I am unable to clear the previous variable that was stored in groupSelected with the new one clicked.
//This grabs the geojson values and stores into the textarea tags in the sidebar
function checkInfo(e) {
var properties = e.target.feature.properties;
$("#nabe-name").html(properties.Name)
$("#top-group").html(properties.largest)
$("#second-group").html(properties.second)
$("#third-group").html(properties.third)
};
//This is where I am stuck:
$(".selected").on("click" ,function(e) {
nabe = $("#nabe-name").val();
var groupSelected= $(".selected").val();
}
<textarea id="nabe-name">Neighborhood</textarea>
<textarea class ="selected" id="top-group">Top</textarea>
<textarea class ="selected" id="second-group">Second</textarea>
<textarea class ="selected" id="third-group">Third</textarea>
I need groupSelected variable to change everytime I click into a different textarea. I have to consider how I close my functions since I need the two variables available to the AJAX request.
Please help. If not obvious, I am new to JS.
Thx
You are always getting the value of the first .selected element. Access the clicked element with this keyword from inside the click event handler.
var groupSelected= $(this).val();
I think groupSelected should be a global variable to hold the previous value and will be changed for the next click .I mean instead of creating in the click as var groupSelected it should be out side of the click listener

jQuery Cloned django-selectable Autocomplete Dropdowns Not Working

As the title states, I have a django-selectable autocomplete form field on my page, that I am trying to clone dynamically after page load. The cloning part works, but the autocomplete fields do not function. I don't know the internals of django-selectable, and I'm still learning jQuery so I am lost as far as figuring out how to "fix" the autoselect functionality.
My general approach is this: I render the form widget using django template variable inside a div container, and I clone the div container.
HTML
<div class="autocomplete" id="autocomplete_{{ form.instance.id}}">{{form.autocomplete_dropdown}}</div>
jQuery
// This works, just the cloned form lacks "autocomplete" functionality.
var autocompleteArray = theDiv.find('.autocomplete');
var acClone = autocompleteArray.clone();
table.find(".column").append(acClone);
Based on SunnySydeUp's comments I made the following revisions:
jQuery
// Clone the div and all its contents
var acClone = autocompleteArray.clone(true, true);
// Get the children of the div
var acCloneChildrenArray = acClone.children();
// iterate through the children array, modify ids where they exist to make them unique
// e.g., unique id contained in idSuffix.
for (var i = 0; i < acCloneChildrenArray.length; i++) {
// if it has an id, make it unique
if (acCloneChildrenArray[i].getAttribute('id')) {
var ident = acCloneChildrenArray[i].getAttribute('id')
acCloneChildrenArray[i].setAttribute('id', ident+'_'+idSuffix);
};
};
Now the data and events are copied, but they are tied to the prototype/master dropdown. That is, clicking one of the cloned objects actually triggers the dropdown for the master. I guess the question comes down to how to attach the event handler dynamically to the new dropdowns?
Final working code (has a minor bug--duplicated dropdown button, but the autocomplete and dropdown functionality works, per SunnySydeUp's solution).
jQuery
// Clone the div
// We leave clone deepcopy flags at default==false
var acClone = autocompleteArray.clone();
// Get the children of the div
// You don't need to set unique id's on the children, it's irrelevant.
// Bind the new selectable fields to the event handlers.
window.bindSelectables('body');
I haven't used django-selectable before but I'm guessing it uses javascript/jQuery to make the autocomplete work. When you call clone, it doesn't clone the javascript for the dropdown. Try doing:
var acClone = autocompleteArray.clone(true);
Passing in true will make jQuery also copy the data and events on that input.
Clone documentation

jQuery selector with variable, not updating when variable changes

'I am trying to do something like the below that will run a function when clicking the "hit" class only when the parent "box" id is correct.
var selected = 1;
$('#box-' + selected + ' .hit').click(function(){
selected = randomFromTo(1,5);
});
The selected variable changes depending on other actions on the page but I can't get the function to work after any change - it stays fixed to the initially selected "box" id. I can imagine why this might be, as jQuery has taken a snapshot of that string and nothing is telling it update it but I can't figure out how to get around this.
Edit: added the selected variable's instantiation and where it gets set. I've left out the UI changes that would make this more understandable to a user.
You could just set the click handler on the .hit class, then check the parent nodes for the right ID within the handler itself. If you do this, make sure you defer the event correctly, otherwise you'll end up with one handler for every single .hit element.
If I understand your problem correctly, the issue is that the selected variable is evaluated at the time your function is created. The function is bound to the click event of a set of DOM elements at that time.
After the binding, it is not possible to modify the selector, and changing any variable used during selection will have no effect.
The best way around this I can think of, is to attach the click event to all possible matches, and then further filter them inside the click handler...
$('.hit').click(function(){
// check if the parent has the correct id
if($(this).parent().attr('id') == 'box-' + selected){
//function
}
});
Could do something like this:
var selected = null;
function setSelected(aSelected) {
if (selected !== null) {
// Remove existing selected click handler
$('#box-' + selected + ' .hit').off('click.selected')
}
selected = aSelected;
// Click handler for new selected el
$('#box-' + selected + ' .hit').on('click.selected', function() { });
}
I would need to see the whole context to understand,
but at the first look it seems the posted code is being run once on your page.
So if You do
var selected = something;
$('#box-' + selected + ' .hit').click(function(){
//function
});
and later on
selected = "newvalue";
this will not change program behaviour, the click event is already set for specific objects.
The other suggestion to set click handler on .hit makes a lot of sense, and then inside the handler You could check if
($(this).parent().attr('id')) == ("box-"+selected)
and only then execute Your code.

Adding an item to an editable menulist when it is typed in by the user

I am creating a menulist popup list dynamically. I the editable and open attributes to true. What I would like to be able to do is to be able to add a new menulist item when the user types in an item that is not already present. Is this possible? If so, how?
I am creating the list like so:
var ml = document.createElement("menulist");
ml.setAttribute("editable","true");
ml.setAttribute("open","true");
ml.setAttribute("oncommand","alert(this.value)");
var mp = document.createElement("menupopup");
var mi = document.createElement("menuitem");
mi.setAttribute("label","item1");
mi.setAttribute("value","1");
mp.appendChild(mi);
//add as many items as i feel
ml.appendChild(mp);
When the value is changed, the alert function is fired when the item is changed but when I type in anything, it is not fired. Basically, I want to eventually pass the value to a funcntion I call from here to add the item to the list.
First of all, you should seriously consider using addEventListener instead of changing attributes like oncommand:
ml.addEventListener("command", function(event) {
alert(this.value);
}, false);
If you want to be notified about changes in the text field then you should add an event listener to the text field:
ml.appendChild(mp);
ml.inputField.addEventListener("input", function(event)
{
alert("Text field value changed");
}, false);
Note that this has to be done after the element is added to the document, before that the inputField property will not be defined (corresponding XBL binding didn't apply yet).
For reference: menulist.inputField, input event

Many to many relationship events with backbone.js

I have a many-to-many relationship with two of my backbone.js models implemented using a pivot table on the serverside. I'm trying to figure out how to structure it clientside. My current structure is:
1) I have a Tag model, and a TagView which renders a checkbox and the tag label, I have a checked event on the checkbox which does nothing at the moment.
I have a TagsCollection, which holds a bunch of Tags.
I have a TagsCollectionView, which binds add, reset etc of the TagsCollection, and adds TagViews for the added Tags, renders them, and appends the html to its current html (on reset, the html is reset).
I have a global TagCollection instance which contains all the possible tags
I have a Notes Model which contains an (empty) TagCollection called selectedtags on init.
The server returns an array of selected tagids for each Notes, which I add to its TagCollection.
Now comes the hard part, tying it all together.. my NotesView has its own TagsCollectionView which is bound to the global TagsCollection (so it can list all the Tags).. now, how do I get a checked event on the checkedbox of its sub TagViews to trigger an add to this Notes model's selectedtags? Should I provide a reference to the this Notes model instance to the TagsCollectionView on init which then provides it to all the TagViews it creates, whose checked event then adds/removes items from that model? That's the best way I can figure out how to do this, any other thoughts would be appreciated.
View is only for visual display of model. Please specify the need for TagsCollectionView in more details:
Use change event for checking the checkbox.
I would advise incremental coding. First work with the Tag and TagView, As it works, continue adding collection to hold the Tags. After that you can add Notes. It's a 'divide and conquer' :)
Don't confuse with the whole design, it is very simple when you start.
I can not provide the whole design due to lack of requirement details. but, I think below code should trigger the starting point of your design.
var TagsCollectionView=Backbone.View.extend({
el:$(),
});
var Tag=Backbone.Model.extend({
defaults:{
// define the default properties
}
});
var TagView=Backbone.View.extend({
el:$("body"),
events:{
'change #checkBox':'customFunction'
},
initialize: function(){
_.bindAll(this, 'render','customFunction');
this.render();
},
render:function(){
var tag=new Tag();
// code to render the checkbox and label
},
customFunction:function(){
// whatever you want to do after checking event of checkbox
}
});
// collection to collect all the tags
var TagCollection=Backbone.Collection.extend({
model:Tag
});
var Notes=Backbone.Model.extend({
defaults:{
'tagCollection':TagCollection
}
});
// you do not require TagsCollectionView

Categories

Resources