Swapping elements in a KnockoutJS observable array [duplicate] - javascript

I have a button that moves an item one position left in an observableArray. I am doing it the following way. However, the drawback is that categories()[index] gets removed from the array, thus discarding whatever DOM manipulation (by jQuery validation in my case) on that node.
Is there a way to swap two items without using a temporary variable so as to preserve the DOM node?
moveUp: function (category) {
var categories = viewModel.categories;
var length = categories().length;
var index = categories.indexOf(category);
var insertIndex = (index + length - 1) % length;
categories.splice(index, 1);
categories.splice(insertIndex, 0, category);
$categories.trigger("create");
}

Here's my version of moveUp that does the swap in one step:
moveUp: function(category) {
var i = categories.indexOf(category);
if (i >= 1) {
var array = categories();
categories.splice(i-1, 2, array[i], array[i-1]);
}
}
That still doesn't solve the problem, though, because Knockout will still see the swap as a delete and add action. There's an open issue for Knockout to support moving items, though. Update: As of version 2.2.0, Knockout does recognize moved items and the foreach binding won't re-render them.

I know this answer comes a bit late, but I thought it might be useful to others who want a more general swap solution. You can add a swap function to your observableArrays like so:
ko.observableArray.fn.swap = function(index1, index2) {
this.valueWillMutate();
var temp = this()[index1];
this()[index1] = this()[index2];
this()[index2] = temp;
this.valueHasMutated();
}
You can then use this function to swap two elements in an array given their indices:
myArray.swap(index1, index2);
For a moveUp function, you could then do something like this:
moveUp: function(category) {
var i = categories.indexOf(category);
if (i > 0) {
categories.swap(i, i+1);
}
}

I had a similar problem as I wanted jQuery drag & drop on my items.
My solution became to use knockoutjs templates to bind the beforeRemove and afterAdd events to the model. The Person Class/function is also a simple knockout view model.
In the below example I use .draggable(), but you could easily use validation. Add your own code for manipulating the observableArray and you should be good to go.
HTML:
<div data-bind="template: {foreach:attendeesToShow, beforeRemove:hideAttendee, afterAdd:showAttendee}">
<div class="person">
<img src="person.jpg" alt="" />
<div data-bind="text: firstName" ></div>
<div class="deleteimg" data-bind="click:$parent.removeAttendee" title="Remove"></div>
</div>
</div>
ViewModel:
var ViewModel = function () {
var self = this;
var at = [new Person('First', 'Person', 'first#example.com'),
Person('Second', 'Person', 'second#example.com')
];
self.attendees = ko.observableArray(at);
self.removeAttendee = function (attendee) {
self.attendees.remove(attendee);
};
this.showAttendee = function (elem) {
if (elem.nodeType === 1) {
$(elem).hide().show("slow").draggable();//Add jQuery functionality
}
};
this.hideAttendee = function (elem) {
if (elem.nodeType === 1) {
$(elem).hide(function () {
$(elem).remove();
});
}
};
};
ko.applyBindings(new ViewModel());

thanks to Michael Best for his version of moveup
my version of moveDown
moveDown: function(category) {
var array = categories();
var i = categories.indexOf(category);
if (i < arr.length) {
categories.splice(i, 2, array[i + 1], array[i]);
}
}

Related

Smart way to add elements to all objects in array? js/jquery/knockout

for example I have an array like this:
var t = ko.observableArray([
{"FoodName":"Rice","FoodOften":"2"},
{"FoodName":"Apple","FoodOften":"3"}
]);
How can I add an static item to all of them like below in a smart way?
[
{"Student":"1","FoodName":"Rice","FoodOften":"2"},
{"Student":"1","FoodName":"Apple","FoodOften":"3"}
]
I know I can loop t().length to add t()[n].Student="1" but just wondering is there any smart way for knockout?
Every variation on answering this question is going to be some form of 'iterate over the array and add the value to each object'. A few:
t.forEach(function(entry) {
entry.Student = 1;
});
var newT = t.map(function(entry) {
entry.Student = 1;
return entry;
});
for (var i = 0; i++; i< t.length) {
t[i].Student = 1;
}
Incidentally, at least in Chrome, jsperf says the first is the most efficient of those three.

angularjs - dynamic function return

index.html
<div ng-repeat="object in objects" ng-style="getStyle(object.id)"></div>
controller.js
[...]
$scope.startWidth = 100;
$scope.getObject = [...]
$scope.getStyle = function(id) {
var widthVal = $scope.startWidth;
$scope.StartWidth += $scope.getObject(id).value;
return { width : widthVal }
}
[...]
This code runs into an infinite $digest loop. I think i know why. The return value should not to be dynamic. But how can i realize that without the loop? I want to display objects with different width. The width of an object depends on a value of the previous object.
The problem is that ng-style puts a $watchExpression on each child scope.
A $watchExpression must be idempotent, means it must return the same with multiple checks.
If a $watchExpression is still dirty another $digest will be triggered.
UPDATE
After I saw GRaAL nice answer I came up with even better solution:
Here is a demo plunker: http://plnkr.co/edit/oPrTiwTOyCR3khs2iVga?p=preview
controller:
$scope.$watchCollection('objects', function calculateWidths() {
var tempWidth = 0;
for( var i = 0; i < $scope.objects.length; i++) {
tempWidth += $scope.objects[i].value;
$scope.objects[i].width = tempWidth + 'px';
}
});
markup:
<div ng-repeat="object in objects" ng-style="{width: object.width}">
old solution
Here is a plunker: http://plnkr.co/edit/CqxOmV5lpZSLKxqT4yqo?p=preview
I agree with Ilan's problem description and solution - it works in case the objects array is not going to change, otherwise the widths will be calculated incorrectly. If new objects may be added/removed in runtime then it is better to recalculate the widths when the collection is changed (see $watchCollection method of scope) or on each digest cycle (for short list of elements it shall be fast enough), something like that:
var widths = [];
$scope.$watch(function calculateWidths() {
var tempWidth = 0;
widths = [];
angular.forEach($scope.objects, function(obj) {
tempWidth += obj.value;
widths.push(tempWidth);
});
});
$scope.getStyle = function(idx){
return {width: widths[idx] + 'px'}
}
Here is the plunker

How to randomly sort list items?

I currently have this code that randomly sorts list items:
var $ul = $('#some-ul-id');
$('li', $ul).sort(function(){
return ( Math.round( Math.random() ) - 0.5 )
}).appendTo($ul);
However, is there any better solution for that?
Look at this question and answer thread. I like this solution via the user gruppler:
$.fn.randomize = function(selector){
var $elems = selector ? $(this).find(selector) : $(this).children(),
$parents = $elems.parent();
$parents.each(function(){
$(this).children(selector).sort(function(){
return Math.round(Math.random()) - 0.5;
// }). remove().appendTo(this); // 2014-05-24: Removed `random` but leaving for reference. See notes under 'ANOTHER EDIT'
}).detach().appendTo(this);
});
return this;
};
EDIT: Usage instructions below.
To randomize all <li> elements within each '.member' <div>:
$('.member').randomize('li');
To randomize all children of each <ul>:
$('ul').randomize();
ANOTHER EDIT: akalata has let me know in the comments that detach() can be used instead of remove() with the main benefit being if any data or attached listeners are connected to an element and they are randomized, detach() will keep them in place. remove() would just toss the listeners out.
I also stuck to such questions I search on google and come across one code. I modify this code for my uses. I also include the shuffle the list after 15 seconds.
<script>
// This code helps to shuffle the li ...
(function($){
$.fn.shuffle = function() {
var elements = this.get()
var copy = [].concat(elements)
var shuffled = []
var placeholders = []
// Shuffle the element array
while (copy.length) {
var rand = Math.floor(Math.random() * copy.length)
var element = copy.splice(rand,1)[0]
shuffled.push(element)
}
// replace all elements with a plcaceholder
for (var i = 0; i < elements.length; i++) {
var placeholder = document.createTextNode('')
findAndReplace(elements[i], placeholder)
placeholders.push(placeholder)
}
// replace the placeholders with the shuffled elements
for (var i = 0; i < elements.length; i++) {
findAndReplace(placeholders[i], shuffled[i])
}
return $(shuffled)
}
function findAndReplace(find, replace) {
find.parentNode.replaceChild(replace, find)
}
})(jQuery);
// I am displying the 6 elements currently rest elements are hide.
function listsort(){
jQuery('.listify_widget_recent_listings ul.job_listings').each(function(index){
jQuery(this).find('li').shuffle();
jQuery(this).find('li').each(function(index){
jQuery(this).show();
if(index>=6){
jQuery(this).hide();
}
});
});
}
// first time call to function ...
listsort();
// calling the function after the 15seconds..
window.setInterval(function(){
listsort();
/// call your function here 5 seconds.
}, 15000);
</script>
Hope this solution helps....Have a great working time ..

Most Efficient Way to Find Leftmost div?

Using jQuery or straight Javascript, I'm looking for the best way to find the leftmost div (or in general the DOM element with the minimum or maximum position on either axis).
So far I have two solutions:
Iterate through the div objects that I want to consider, saving the smallest left position found.
Build an array of objects and using javascript's sort() function with a comparison function that looks at the left property, then choosing the 0th element.
I know that solution 1 would be O(N), but I'm not sure what the efficiency of the sort() function is in most browsers, or if there is a completely different approach.
Consider this:
you keep track of one element and one position, you access each element once
you keep track of all elements and access all positions multiple times because of sorting
What do you think is the fastest? :)
option 1: iterate through it only once
var $smallest = {left: 000000000, item: null};
var $left = 0;
$('selector').each(function(){
$left = $(this).offset().left;
if ($left < $smallest.left)
{
$smallest.left = $left;
$smallest.item = this;
}
});
option 2: iterate through it at least twice
var $array = [];
$('selector').each(function(){
var $this = $(this);
$array.push({left: $this.offset().left, item: this});
});
$array.sort(function(a,b){
if (a.left < b.left) return -1;
if (a.left > b.left) return 1;
return 0;
});
// smallest is $array[0]
option 1 is always faster in this case since you only have to sort it while selecting, sorting almost comes for free in that case.
edit: of course, using only the DOM for this is again, way faster.
Probably not going to do any better than O(n) and the best you'll do pure sorting is O(nlogn). The fastest way would be to walk the DOM. I would call getElementsByTagName("div") and iterate through keeping track of the left most element.
function findLeftMostDiv() {
var leftest = { "left": 999999999999, elem: null };
var divs = document.getElementsByTagName("div");
for(i=0; i<divs.length; i++) {
var div = divs[i];
var curleft = findPos(div);
if(curleft < leftest.left) {
leftest.left = curleft;
leftest.elem = div;
}
}
return leftest.elem;
}
function findPos(obj) {
var curleft=0;
if(obj.offsetParent) {
do {
curleft += obj.offsetLeft;
} while (obj = obj.offsetParrent);
}
return curleft;
}

How can I optimize my routine to build HTML tiles from an array?

As my last question was closed for being "too vague" - here it is again, with better wording.
I have a "grid" of li's that are loaded dynamically (through JavaScript/jQuery), the Array isn't huge but seems to take forever loading.
So, SO people - my question is:
Am I being stupid or is this code taking longer than it should to execute?
Live demo: http://jsfiddle.net/PrPvM/
(very slow, may appear to hang your browser)
Full code (download): http://www.mediafire.com/?xvd9tz07h2u644t
Snippet (from the actual array loop):
var gridContainer = $('#container');
var gridArray = [
2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,2,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,2,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
];
function loadMap() {
var i = 0;
while (i <= gridArray.length) {
var gridHTML = $(gridContainer).html();
$(gridContainer).html(gridHTML+'<li class="node"></li>');
i++;
}
$('li.node').each(function() {
$(gridArray).each(function (i, val) {
if (val == '0') { gridTile = 'grass.jpg' };
if (val == '1') { gridTile = 'mud.jpg' };
if (val == '2') { gridTile = 'sand.gif' };
$($('ul#container :nth-child('+i+')'))
.css({ 'background-image': 'url(img/tiles/'+gridTile });
});
});
}
The loop where you set the background images is the real problem. Look at it: you're looping through all the <li> elements that you just got finished building from the "grid". Then, inside that loop — that is, for each <li> element — you go through the entire "grid" array and reset the background. Each node will end up being set to the exact same thing: the background corresponding to the last thing in the array over and over again to the exact same background.
The way you build the HTML is also very inefficient. You should loop through the grid and build up a string array with an <li> element in each array slot. Actually, now that I think of it, you really should be doing the first and second loops at the same time.
function loadMap() {
var html = [], bg = ['grass', 'mud', 'sand'];
for (var i = 0, len = gridArray.length; i < len; ++i) {
html.push("<li class='node " + bg[gridArray[i]] + "'></li>");
}
$(gridContainer).html(html.join(''));
}
Now you'll also need some CSS rules:
li.grass { background-image: url(grass.jpg); }
li.mud { background-image: url(mud.jpg); }
li.sand { background-image: url(sand.gif); }
It'd probably be farm more efficient to build up the complete HTML for the array and then assign it to the .html property of the container, rather than assigning each individual li:
var gridHTML = $(gridContainer).html();
while (i <= gridArray.length) {
gridHTML = gridHTML+'<li class="node"></li>';
i++;
}
$(gridContainer).html();
Next, why are you looping over both of these? The outer loop is probably completely unnecessary, because your inner loop already uses nth-child to select the proper node.
$('li.node').each(function() {
$(gridArray).each(function (i, val) {

Categories