What's the best way to re-initialize javascript without a page refresh
I'm currently trying to append an MDBootstrap <select> tag, which is not as simple as adding a child element. Instead I'm removing the element and reconstructing it with the updated data via AJAX request.
At the moment, the only possibility I see is just executing the code again after the element is recreated.
Apologies if this isn't clear enough.
What I'm attempting to try, which works, however it's not very clean:
$("#function-btn").click(function(){
$.get("api/endpoint/getprofiles", function(){}).done(function(data){
$(".select-wrapper.mdb-select.md-form").remove()
$("#charcontainer").html(data);
$('.mdb-select').materialSelect();
})
// Reinitialize other JQuery functions around the '.mdb-select' element (alot)
})
Consider the following html
<div id='wrapper'>
<div id='container'>
<span>Content</span>
</div>
</div>
If you're deleting and replacing #container you will not want to hook your selector on #container but rather your jQuery should hook onto the parent (#wrapper) first and then drill down.
Therefore it will look something like this.
$('#wrapper>#container').on('click',function(){
//do the thing
});
That way you're technically not hooking onto the element that's removed from the DOM but rather the parent (#wrapper) element even though the selector has the child.
I am using a knockout foreach (more specifically, template: { foreach: items }) binding to display a list of elements.
I then proceed to take the following actions:
Swap the first and second elements of the observable array. I see the changes reflected on screen, as expected.
Repeat the previous action to revert to the initial state. Again, this works as expected.
Now, swap the first and second DOM elements. I see the changes reflected on screen, as expected.
Repeat the previous action to revert to the initial state. Again, this works as expected.
Even though we have manually tampered with the DOM, we have reverted to exactly the initial state, without invoking knockout during the DOM tampering. This means the state is restored to the last time knockout was aware of it, so it should look to knockout as if nothing ever changed to begin with.
However, if I perform the first action again, that is, swap the first two elements in the array, the changes are not reflected on screen.
Here is a jsfiddle to illustrate the problem: https://jsfiddle.net/k7u5wep9/.
I know that manually tampering with the DOM managed by knockout is a bad idea and that it can lead to undefined behaviour. This is unfortunately unavoidable in my situation due to third party code. What stumps me is that, even after reverting the manual edits to the exact initial state, knockout still does not work as expected.
My question is: what causes this behaviour?
And then, how does one work around it?
Turns out there is nothing magical happening here. The mistake I made was to only consider elements instead of all nodes. The knockout template binding keeps a record of all nodes when reordering, not just elements.
Before manually editing the DOM, the child nodes of the template binding are:
NodeList(6) [text, div, text, text, div, text].
After manually swapping the first two elements using parent.insertBefore(parent.children[1], parent.children[0]), this turns into:
NodeList(6) [text, div, div, text, text, text].
Repeating the action yields:
NodeList(6) [text, div, div, text, text, text].
Although this is identical to the initial state when only referring to elements, it is quite different when referring to all nodes.
The solution now becomes clear. One way to perform a proper manual swap is to replace
parent.insertBefore(parent.children[1], parent.children[0]);
with
let nexts = [parent.children[0].nextSibling, parent.children[1].nextSibling];
parent.insertBefore(parent.children[1], nexts[0]);
parent.insertBefore(parent.children[0], nexts[1]);
as seen in https://jsfiddle.net/k7u5wep9/2/.
Obviously more care has to be taken when there are no text nodes before/after, but the idea remains the same.
By manipulating the DOM, you have broken the binding made.
Do not manipulate directly the DOM. Knockout will not detect the changes made.
If you put a with: items around your foreach, it at least keeps working but requires double click if dom order != array order .. might get you on track atleast, maybe you can re-order the ko-array inside the dom function to keep their 'orders' in sync?
let vm = {
items: ko.observableArray(['item1', 'item2']),
reorder_array() {
vm.items([vm.items()[1], vm.items()[0]]);
},
reorder_dom() {
let parent = document.querySelector('#items');
parent.insertBefore(parent.children[1], parent.children[0]);
vm.reorder_array();
}
};
ko.applyBindings(vm);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<div data-bind="with: items">
<div id="items" data-bind="template: { foreach: $data }">
<div data-bind="text: $data"></div>
</div>
</div>
<button data-bind="click: reorder_array">Reorder array</button>
<button data-bind="click: reorder_dom">Reorder DOM</button>
<div>
Reorder the array twice, then reorder DOM twice. This should work as expected, and end up with the initial state. Then, try to reorder the array again. It should not work. Why?
</div>
I have a loading view that's set as the html inside the directive. Something like the following.
<my-directive>
<div class="loader"></div>
</my-directive>
I'd like the HTML inside there to be active until some property of my directive has finished processing.
The closest thing I've found is transclude but I'm not sure that's really what I want.
Is there a way to avoid rendering until some arbitrary point in time?
you can hide a html element until a property return true
<div ng-if="taskFinished"></div>
or
<div ng-show="taskFinished"></div>
both do the same think but with ng-show the hidden content are not recalculated on each unhide
What Alainlb explained is also how I would approach the issue, show in the example below:
<my-directive>
<div ng-if="loading">
<div class="loader"></div>
</div>
<div ng-if="!loading">
<span>Do everything in here...</span>
</div>
</my-directive>
ng-if and ng-show/ng-hide
I personally will always use ng-if unless there is a very good reason not to. Since ng-if will only render the HTML when the check has passed. ng-hide on the other hand will make the HTML code invisible but it is still there! When the directive is very complex, then the browser is doing pointless work calculating and rendering things the user can't even see. To me, the only time it makes sense is when you want to collapse items that you know for sure the user will open very often. Even in this case I would still consider ng-if, unless it takes a long time to render each time.
Got a little situation here.
I'm just learning Angular and I'm stuck with the following problem.
I've a list that repeats itself (ng-repeat) and when it is rendered, you can click on it and I add a class to the list-item you clicked on, jQuery-style:
$('#'+id).addClass("myClass");
And the id is stored in a scope-variable.
But when I change the scope for the list-item, new items are loaded, and when I change them back to the state were I started, I'm trying to add the class to the same id, but doens't work.
Do I miss something?
Thanks in advance
A better approach here would be to alter your data on click and then to conditionally attach a class based on the data:
<div ng-repeat="item in items" ng-class="{myClass:item.changed}">
{{item.name}}
<button ng-click="item.changed = !item.changed">Change me!</button>
</div>
Here it is in action: http://jsfiddle.net/wilsonjonash/NTpLS/
As I mentioned in the comments, when programming in angular, think in terms of your model first. Most things you would achieve in jQuery-land with DOM manipulations can be achieved in angular with model-dependent markup (directives).
Best of luck!
You can use the ng-class instead of addClass : )
ngClass Document
I ran into an issue trying to find the closest sibling of a parent that contained a specific child (one tree level). I finally found the answer through trial and error, but I searched on here before proceeding with testing. Thought I'd share my findings and see if anyone has an easier way to do what I'm doing.
Essentially I'm using jQuery 1.7 (latest as of 03/30/2012) and needed to find a title previously displayed for a "continue" heading in the following row. Here's what I came up with:
(($(this).parent().prevAll()).children('h2')).first().html();
I simply assigned this to a variable and used it to create a new "continue" title on the next row.
Any suggestions for cleaner code? Can this be done easier or more efficiently?
EDIT:
Here's the HTML as requested:
<div class="container">
<div class="subbox">
<h2>Title1</h2>
<div class="itembox">...some content here...</div>
</div>
<div class="subbox"><div class="itembox">...some content here...</div></div>
</div>
As you can see, it's simply a set of sub-boxes with the first one in the set having an H2, but in my case I was working with 4+ sets and needed to get the title of the first item in the set (not necessarily the title of the first set).
Not 100% sure if this will work since i haven't tested it, but you could simplify the code if you would have something like:
($(this).parent().prevAll()).children("h2:first").html();