angularjs - dynamic function return - javascript

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

Related

Reset the amended values in the Else part of an if/else statement by removing the function

Is there an equivalent in JS of the initial value in CSS. When you have a function that you want to behave in a certain way with an if statement but, then in the else part you want the values to just be what they they were before the if piece of code returned true. I've always re-written the original values as part of the else, but this seems to be a galatically inefficient way of doing things, for example:
var a = something, b = something_else;
if (a) {
run a function which changes lots of values;
} else {
re-type the values to what they were before the function ran;
}
A more concrete version of what I'm trying to do is below. I have a forEach method that changes some string values. If I want to set it so that on the else all the code in the initial if is ignored I know I can do this by copy and pasting the code under a different function name and setting the second slice value to 300, which is the length of the original strings, but this seems a very verbose way of doing things?
There must be a way of setting the else code so it removes / kills off the original myresize() function so all the original values hold true?
var content = document.querySelectorAll(".generic-content p");
function textSlice() {
if (window.innerWidth < 500) {
function myresize() {
content.forEach(function(index) {
var x2, x3, x4;
x2 = index.textContent;
x3 = x2.slice(0, 100) + "[...]";
index.textContent = x3;
});
myresize();
}
} else {
// remove the myresize(); function or somehow kill it
}
}
addEventListener("resize", textSlice, false);
There is no built-in feature in JavaScript that restores the initial state of the elements. However, it is relatively easy to build such a feature. Before you start, save the state to a global object, which you can then use to restore the initial state whenever you want.
Try the code below. Note that the first parameter of the forEach method is the element itself, not the index. So it isn't right to name it index. I've changed it to item.
var content = document.querySelectorAll(".generic-content p");
//Save the initial state.
var initial = [];
(function() {
content.forEach(function(item) {
initial.push(item.textContent);
});
})();
function textSlice() {
if (window.innerWidth < 500) {
content.forEach(function(item) {
var x2, x3, x4;
x2 = item.textContent;
x3 = x2.slice(0, 100) + "[...]";
item.textContent = x3;
});
} else {
//Restore the initial state.
content.forEach(function(item, index) {
item.textContent = initial[index];
});
}
}
addEventListener("resize", textSlice, false);
<div class="generic-content">
<h4>Window Resize Demo</h4>
<p>first paragraph</p>
<p>second paragraph</p>
<p>third paragraph</p>
</div>

JS multidimensional array spacefield

i wanna generate a 3x3 field. I want to do this with JS, it shall be a web application.
All fields shall inital with false. But it seems so that my code is not working correctly, but i don't find my fault. The goal is, that every spacesector is accessible.
Thats my idea:
// define size
var esize = generateSpace(3);
}
space[i] = false is replacing the array with a single boolean value false, not filling in all the entries in array you just created. You need another loop to initialize all the elements of the array.
function generateSpace(x) {
var space = [];
for (var i = 0; i < x; i++) {
space[i] = [];
for (var j = 0; j < x; j++) {
space[i][j] = false;
}
}
return space;
}
Also, your for() loop condition was wrong, as you weren't initializing the last element of space. It should have been i < space.length.
And when it's done, it needs to return the array that it created.
Since I got somewhat bored and felt like messing around, you can also initialize your dataset as shown below:
function generateSpace(x) {
return Array.apply(null, Array(x)).map(function() {
return Array.apply(null, Array(x)).map(function() {
return false;
});
});
}
The other functions work equally well, but here's a fairly simply looking one using ES6 that works for any square grid:
function generateSpace(x) {
return Array(x).fill(Array(x).fill(false));
}

Looping through objects in an array JS

I'm putting together this script which pulls two child elements from a containing div #mini_ads, adds them to an array. I want to be able to use the array to select them via index in order to manip. them individually.
I know I can just select them w/o even using an array of course, but I want this array as I may add multiple more elements later.
The issue is that I am not able to select the items individually by their index in the array. The current script I've got going is selecting and manipulating both objects in the array as if they're both index[0].
var miniAds = $('#mini_ads');
var elements = miniAds.children();
var changeWtime;
var adsArr = new Array();
var i = 0;
var x = 0;
adsArr.push(elements);
console.log(adsArr);
adsArr[i].css("display", "none");
var changeWtime = setInterval(function () {
for (x; x < 1; x++) {
return x;
while (x > i) {
adsArr[1].css("display", "block");
}
};
}, 5000);
console.log(x);
changeWtime;
I am not sure where I'm going wrong here. Assistance will be much appreciated. Thanks in advance.
Issues with your code
You're creating a double array when you push elements into 'adsArr':
adsArr.push(elements);
You're throwing a return statement in the for loop:
for (x; x < 1; x++ ){
return x;
// ...
You have a double loop for no reason while inside of the for.
Solution
I was going to explain the solution to this verbally, but after coding an example I realized that there is too much to explain this is another solution similar to yours:
var miniAds = $('#mini_ads'),
elements = miniAds.children(),
i = 2,
x = 0;
elements.hide();
var changeWtime = setInterval(function () {
if ( x < i ) {
$(elements[x]).show();
}
x++;
}, 5000);
Link to example on jsbin.
Hi u should push child divs as below function does and after that i believe u can perform ur task...
var adsArr= [];
$('#mini_ads').children().each(
function(i){
adsArr.push(this);
});
In plain Javascript use .styles()
.css() which is a JQuery method but not Javascript
ref http://www.w3schools.com/js/js_htmldom_css.asp

Swapping elements in a KnockoutJS observable array [duplicate]

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]);
}
}

Looping through an object with incremental property names

I'm trying to loop through an object like you would an array. I'm struggling to append the loop counter to the variable name.
I have an object like this (output with dump(), which I found here):
object(2): {
elem0: array(4): {
[0]: string(27): "http://placehold.it/300x300"
[1]: string(3): "0.8"
[2]: string(4): "-150"
[3]: string(3): "200"
}
elem1: array(4): {
[0]: string(27): "http://placehold.it/300x300"
[1]: string(3): "0.6"
[2]: string(3): "-70"
[3]: string(3): "458"
}
}
Here's how I'm trying to loop through it:
jQuery(document).ready(function($) {
// Provides object-measuring functionality
Object.size = function(obj) {
var size = 0, key;
for (key in obj) {
if (obj.hasOwnProperty(key)) size++;
}
return size;
};
// Returns the number of objects in my object
var size = Object.size(window.depthElems);
/*
This is where I'm having difficulty.
I would like to use window.depthElems.elem0,
then window.depthElems.elem1, etc.
*/
for (var i = 0; i < size; i++) {
$('.wrapper').append('<img src="' + window.depthElems.elem+i+[0] + '" />');
}
});
I will, for the sake of argument, also provide my question as answer. You can use:
for(element in window.depthElems) {
if(window.depthElems.hasOwnProperty(element)) {
$('.wrapper').append('<img src="' + window.depthElems[element] + '" />');
}
}
This is not only more elegant, but also requires far less code. Of course if there is a reason to use the other code, please say so.
Note: This code is edited to also include the ability to read 'arrays', however the question was to make it work with 'objects'. If you use 'objects' the 'hasOwnProperty' check is superfluous.
Note #2: You can also use var hasOwn = Object.prototype.hasOwnProperty; like Azder said, which is a nice safeguard.
I apologize if my answer is over the top, I just like to prevent further hurt by miss-using JS (which I have experienced a lot) .
jQuery(document).ready(function($) {
var i; // there is no block scope in JS, so better to be clear and define i here
var $wrapper; // also
// Changing the JS built-in objects is problematic most of the time
// You should learn from jQuery and do wrapping instead
// Or at least just a good namespasing like:
// MyFramework.objectSize = function (obj) {}
Object.size = function(obj) {
var size = 0, key;
var hasOwn = Object.prototype.hasOwnProperty; // will explain further down
for (key in obj) {
// if obj has redifined hasOwnProperty = function(){ return false; }?
// it's better to use hasOwn like this if(hasOwn.call(obj,key)) {}
// and please do use braces even if only 1 statement
if(hasOwn.call(obj,key)) size++;
}
return size;
};
// Returns the number of objects in my JSON object
var size = Object.size(window.depthElems);
$wrapper = $('.wrapper'); // cached so jQuery doesn't search for it each iteration
// i is scoped to the whole function anyways
for (i = 0; i < size; i++) {
// $.each even guards you of the changing DOM which can cause
// infinite loops (you don't have that problem here, but... good to know
$.each(window['depthElems'+i],function(index,element){
$wrapper.append('<img src="' + element + '" />');
});
}
});
Also, since you already make objects named elem1, elem2, elem3,... you might as well use a two dimensional array, like window.depthElems = [[],[],[]]

Categories