jQuery, next, prev, cycling to first and last - javascript

Sorry for this confusing title.
What i'm trying to do is a function (or just a simple way), which will do simple .next(), but if there's no next element, match first. And the same for .prev() - if there's no previous element, match last.
So i made it this way:
var current_selected = getSelected();
if(current_selected.length) {
var prev = current_selected.prev();
if(prev.length) {
setSelected(prev);
return;
}
}
setSelected(getLast());
But i don't really like it, i think there's some pretty way do it. Any thoughts?
(getSelected and getLast returns jQuery objects.

You could create some little convenience plugins:
$.fn.nextWrap = function() {
var $next = this.next();
if ($next.length) return $next;
return this.siblings().first();
};
$.fn.prevWrap = function() {
var $prev = this.prev();
if ($prev.length) return $prev;
return this.siblings().last();
};
Then you can simply do $('#something').nextWrap() or $('#something').prevWrap().
Here's a quick demo: http://jsfiddle.net/qpDKL/
Note: This will behave mostly like prev() and next() (with the wrap behavior, of course), but it doesn't support the prev|next(selector) syntax.
Edit: Here's a slightly more terse plugin syntax since they're nearly the same anyway:
$.each(['next', 'prev'], function(i, nextOrPrev) {
$.fn[nextOrPrev + 'Wrap'] = function() {
var $item = this[nextOrPrev]();
if ($item.length) return $item;
return this.siblings()[nextOrPrev === 'next' ? 'first' : 'last']();
};
});

This will work for prev case
var current_selected = getSelected();
var prev = current_selected.prev();
if(prev.length) {
setSelected(prev);
} else {
setSelected(getLast());
}

The best way that I can think of would be to have an array of the elements that you want to cycle. You can cycle through an array in two ways:
array.push(array.shift());
or
var count = 0;
function cycle() {
return array[count++ % array.length];
}
I think the former looks cleaner.
http://jsfiddle.net/ExplosionPIlls/feeF5/

Something around these lines:
if (!$('selected').next().length)
return $('selected').parent().children().first();
else
return $('selected').next();
if (!$('selected').prev().length)
return $('selected').parent().children().last();
else
return $('selected').prev();

Related

How can I insert an argument like forEach, reduce and the like does?

I'm trying to reinvent the wheel, sort of.. Just messing around trying to remake some jquery functions.. I've come this far
var ye = function (ele) {
if (ele[0] == "#")
{
return document.getElementById(ele.slice(1));
}
else if (ele[0] == ".")
{
// returns an array, use index
return document.getElementsByClassName(ele.slice(1));
}
else
{
// also returns an array
return document.getElementsByTagName(ele);
}
}
but how can I use this element as a parameter in a function in the 'ye' prototype. For example, if I wanted to make fontsize how could I get the dom element like here:
ye.prototype.fontSize = function (ele)
{
ele.style.fontSize = "30px";
}
Just to add a bit to make the title relevant.. forEach inserts three arguments into the callback function, just like I want ye to insert ele into the fontSize function.
Just messing around trying to remake some jquery functions...
...but how can I use this element as a parameter in a function in the 'ye' prototype..
Here is a very crude and simple way to start...
Create a function with a property called elems which is an array and will store the selected DOM elements.
Like this:
var oye = function() { this.elems = []; };
On its prototype, you can create your custom functions which you want to expose. e.g. the function fontSize (as in your question), iterate over the elems array property that we created earlier changing the font size of each DOM element stored in. this points to the instance which is calling this function which we will ensure to be of type oye later on. To enable chaining, we simply return itself via this.
Like this:
oye.prototype.fontSize = function(size) {
this.elems.forEach(function(elem) {
elem.style.fontSize = size;
});
return this;
};
Now create the selector function called ye. This serves the purpose of selecting the DOM elements, storing them in the elems array property of a new instance of oye class, and return the instance. We call the slice of the array prototype to convert the nodeList to an array.
Like this:
var ye = function(elem) {
var newOye = new oye;
newOye.elems = [].slice.call(document.querySelectorAll(elem));
return newOye;
};
Now start using it in your code. Just like jQuery, you can use ye to select and then call your custom functions.
Like this:
ye("#elem1").fontSize('30px');
Just like jQuery, you can also chain multiple custom functions as shown in the complete working example below:
ye("P").fontSize('24px').dim(0.4);
Next step: Remember this is just a very crude example. You can now proceed to club the step 1 and 2 into a single call using the init pattern returning the new object from the selector function itseld. Learn more about Javascript and best practices.
Here is a sample working demo:
var oye = function() { this.elems = []; };
oye.prototype.fontSize = function(size) {
this.elems.forEach(function(elem) {
elem.style.fontSize = size;
});
return this;
};
oye.prototype.dim = function(value) {
return this.elems.forEach(function(elem) {
elem.style.opacity = value;
});
return this;
};
var ye = function(elem) {
var newOye = new oye;
newOye.elems = [].slice.call(document.querySelectorAll(elem));
return newOye;
};
ye("#elem1").fontSize('30px');
ye(".elem2").fontSize('20px');
ye("P").fontSize('24px').dim(0.4);
<div>This is normal text.</div>
<div id="elem1">size changed via id.</div>
<div class="elem2">size changed via class.</div>
<div class="elem2">size changed via class.</div>
<p>size changed and dimmed via tag name</p>
<p>size changed and dimmed via tag name</p>
Regarding your question, I may think you're new to JavaScript, or not familiar with its basic concepts. I'm not sure reinventing the wheel is a good thing in such conditions.
Since you've cited jQuery, you can have a look at its source code to understand how it works under the hood:
https://github.com/jquery/jquery/blob/99e8ff1baa7ae341e94bb89c3e84570c7c3ad9ea/src/core.js#L17-L23
https://github.com/jquery/jquery/blob/99e8ff1baa7ae341e94bb89c3e84570c7c3ad9ea/src/core.js#L38-L81
https://github.com/jquery/jquery/blob/99e8ff1baa7ae341e94bb89c3e84570c7c3ad9ea/src/core/init.js#L19-L114
Having that said, I would have done something like this:
var ye = function ( ele ) {
return new ye.prototype.init(ele);
};
ye.prototype.init = function( ele ) {
this._elements = [].slice.call(document.querySelectorAll(ele));
return this;
};
ye.prototype.forEach = function( fn ) {
this._elements.forEach(fn);
return this;
};
ye.prototype.fontSize = function( fontSizeValue ) {
this.forEach(function (ele) {
ele.style.fontSize = fontSizeValue;
});
return this;
};
The associated usage is as follow:
var myCollection = ye('.someClassName');
myCollection.forEach(function ( item, index ) {
console.log(item.style.fontSize);
});
myCollection.fontSize('45px');
myCollection.forEach(function ( item, index ) {
console.log(item.style.fontSize);
});
Use ye function calling before setting style, something like:
ye.prototype.fontSize = function(ele) {
ye(ele).style.fontSize = '30px';
}
returned object should be richer, like that:
var baseObject = {
// Will be used for the element:
element: null,
width: function(){ return this.element.getwidth(); /* or anything similar*/ }
// ... Further methods
}
and then in your ye function:
var ye = function (ele) {
var yeElem = clone(baseObject); // See comment below!!
if (ele[0] == "#") { yeElem.element = document.getElementById(ele.slice(1)); }
else if (ele[0] == "."){ /*...*/ }
else { /*...*/ }
return yeElem;
}
This way the new element has built in methods.
As for the clone() method used, it doesn't exist but you have to use some clone method.
I recommend Loadsh's _.cloneDeep() (here).

Javascript: move objects from one array to another: Best approach?

I have two arrays, called 'objects' and 'appliedObjects'. I'm trying to come up with an elegant way in Javascript and/or Angular to move objects from one array to another.
Initially I did something like this:
$scope.remove = function () {
angular.forEach($scope.appliedObjects, function (element, index) {
if (element.selected) {
element.selected = false;
$scope.objects.push(element);
$scope.appliedObjects.splice(index, 1);
}
});
}
$scope.add= function () {
angular.forEach($scope.objects, function (element, index) {
if (element.selected) {
element.selected = false;
$scope.appliedObjects.push(element);
$scope.objects.splice(index, 1);
}
});
}
But then I realized that when the value was removed from the looping array, and it would not add or remove every other item, since it went by index.
Then I tried using a temporary array to hold the list of items to be added or removed, and I started getting strange referential issues.
I'm starting to spin a bit on what the best solution to this problem would be...any help and/or guidance would much appreciated.
function moveElements(source, target, moveCheck) {
for (var i = 0; i < source.length; i++) {
var element = source[i];
if (moveCheck(element)) {
source.splice(i, 1);
target.push(element);
i--;
}
}
}
function selectionMoveCheck(element) {
if (element.selected) {
element.selected = false;
return true;
}
}
$scope.remove = function () {
moveElements($scope.appliedObjects, $scope.objects, selectionMoveCheck);
}
$scope.add = function () {
moveElements($scope.objects, $scope.appliedObjects, selectionMoveCheck);
}
When a construct does too much automatically (like forEach, or even a for-loop, in this case), use a more primitive construct that allows you to say what should happen clearly, without need to work around the construct. Using a while loop, you can express what needs to happen without resorting to backing up or otherwise applying workarounds:
function moveSelected(src, dest) {
var i = 0;
while ( i < src.length ) {
var item = src[i];
if (item.selected) {
src.splice(i,1);
dest.push(item);
}
else i++;
}
}
You are altering the array while iterating on it, you will always miss some elements.
One way of doing it would be to use a third array to store the references of the objects that need to be removed from the array:
// "$scope.add" case
var objectsToRemove = [];
$scope.objects.forEach(function (value) {
if (value.selected) {
value.selected = false;
$scope.appliedObjects.push(value);
objectsToRemove.push(value);
}
});
objectsToRemove.forEach(function (value) {
$scope.objects.splice($scope.objects.indexOf(value), 1);
});
If you wish to move simply whole array you could do:
appliedObjects = objects;
objects = []
Of course it won't work if they were parameters of a function!
Otherwise I cannot see other way than copying in the loop, e.g.
while (objects.length) {
appliedObjects.push(objects[0]);
objects.splice(0,1);
}
or if you like short code :) :
while (objects.length) appliedObjects.push(objects.splice(0,1));
check fiddle http://jsfiddle.net/060ywajm/
Now this maybe is not a fair answer, but if you notice you are doing alot of complicated object/array manipulations, you should really check out lodash or underscore library. then you could solve this with on liner:
//lodash remove function
appliedObjects.push.apply( appliedObjects, _.remove(objects, { 'selected': true}));
//or if you want to insert in the beginning of the list:
appliedObjects.splice(0, 0, _.remove(objects, { 'selected': true}));
This is a first pass at what I think will work for you. I'm in the process of making a test page so that I can test the accuracy of the work and will update the tweaked result, which hopefully there will not be.
EDIT: I ran it and it seems to do what you are wanting if I understand the problem correctly. There were a couple of syntax errors that I edited out.
Here's the plunk with the condensed, cleaned code http://plnkr.co/edit/K7XuMu?p=preview
HTML
<button ng-click="transferArrays(objects, appliedObjects)">Add</button>
<button ng-click="transferArrays(appliedObjects, objects)">Remove</button>
JS
$scope.transferArrays = function (arrayFrom, arrayTo) {
var selectedElements;
selectedElements = [];
angular.forEach(arrayFrom, function(element) {
if (element.isSelected) {
element.isSelected = false;
selectedElements.push(element);
}
});
angular.forEach(selectedElements, function(element) {
arrayTo.push(arrayFrom.splice(
arrayFrom.map(function(x) {
return x.uniqueId;
})
.indexOf(element.uniqueId), 1));
});
};
Old code
$scope.remove = function () {
var selectedElements;
selectedElements = [];
angular.forEach($scope.appliedObjects, function (element) {
if (element.isSelected) {
element.isSelected = false;
selectedElements.push(element);
}
});
angular.forEach(selectedElements, function (element) {
$scope.objects.push($scope.appliedObjects.splice(
$scope.appliedObjects.map(function (x) { return x.uniqueId; })
.indexOf(element.uniqueId), 1));
});
};
$scope.add = function () {
var selectedElements;
selectedElements = [];
angular.forEach($scope.objects, function (element) {
if (element.isSelected) {
element.isSelected = false;
selectedElements.push(element);
}
});
angular.forEach(selectedElements, function (element) {
$scope.appliedObjects.push($scope.objects.splice(
$scope.objects.map(function (x) { return x.uniqueId; })
.indexOf(element.uniqueId), 1));
});
};
You can use this oneliner as many times as many items you need to move from arr1 to arr2 just prepare check func
arr2.push(arr1.splice(arr1.findIndex(arr1El => check(arr1El)),1)[0])
You can use this to concat 2 arrays:
let array3 = [...array1, ...array2];

Curious if angularJs has better method to calculate number of checkboxes

I need to check if any checkbox is checked. so i am doing it like
self.isButtonEnabled = function() {
var selectLineCheckboxs = document.getElementsByClassName('selectLineRadioInput'),
i = 0, checkboxLength = selectLineCheckboxs.length - 1;
for (i = 0; i <= checkboxLength; i++) {
if (selectLineCheckboxs[i].checked) {
self.selectLineChecked = true;
break;
} else {
self.selectLineChecked = false;
}
}
return self.selectLineChecked;
};
in return i get true if any checkbox is checked.
so quite simple,
Now here i am looking if we can do the same with angularJs with any better approach and i do not want to use watch() function in angular.
I can help with your some code to convert it to look like in Angular way.
use angular.element (provided by jQLite to get element) as instead of document.getElementsByClassName
You could use $filter while checking attribute is checked or not
CODE
self.isButtonEnabled = function() {
var selectLineCheckboxs = angular.element('.selectLineRadioInput');
var checkedValues = $filter('filter')(selectLineCheckboxs, { 'checked': true }); //do filtering and contains check value
self.selectLineChecked = checkedValues.length > 0 ? true : false;
return self.selectLineChecked;
};
Note: You should add $filter dependency on your controller before using $filter
Update
I'd suggest you to create your own custom filter that could be usable in multiple purposes, or dynamically check property value is true or not. I know your code is as same as you ask in answer, but I putted some of your code as reusable component, which can dynamically work for any property value to check is true or not.
Filter
.filter('isPropertyTrue', function () {
return function (elements, property) {
var returnArray = [];
angular.forEach(elements, function (val, index) {
if (val[property]) returnArray.push(val)
});
return returnArray;
}
});
Code
$scope.isButtonEnabled = function () {
var selectLineCheckboxs = document.getElementsByClassName('selectLineRadioInput');
var checkedValues = $filter('isPropertyTrue')(selectLineCheckboxs, 'checked');
self.selectLineChecked = checkedValues.length > 0 ? true : false;
return self.selectLineChecked;
};
JSFiddle
Hope this could help you, Thanks.
i have been using it this way with ng-bind variable in scope $scope.Items .and the binded variable can be used to see what all items are checked.
angular.forEach($scope.Items,function(key,value)
{
if(key.Selected)
{
counter++;
}
});
Here is a JSFiddle to illustrate the same

Use function on all elements in document

I've created a little plugin to check if an element is in position : fixed and to get it's height if it is. What I want is to run this function through all elements in my document to find all fixed elements and get their heights, but I dont know how to write it down
$.fn.isFixed = function () {
if ($(this).css('position') === 'fixed'){
var height = ($(this).height());
return height;
}
else {
return false;
};
}
This is my code.
In that case you may have to return an array since there can be more than one element. Also to make sure that the value indexes are maintained I'm inserting undefined to the array if the position is not fixed
$.fn.isFixed = function () {
var array = [];
this.each(function(){
if ($(this).css('position') === 'fixed') {
array.push($(this).height());
}else{
array.push(undefined);
}
})
return array;
}
var allElements = $("*").contents();
Then use .each() function on allElements variable.
Hope this helps
This search only in declared stylesheets.
var selectorsArray = [];
for (i in document.styleSheets) {
for (k in document.styleSheets[i].cssRules) {
if (document.styleSheets[i].cssRules[k].style.position === "fixed") {
selectorsArray.push(document.styleSheets[i].cssRules[k].selectorText);
}
}
}
So the rest of elements you can select with:
$('[style~=fixed]').each(function(i,e){});

How to find input element id by value?

How do I get the id of an input element based on its value? The values will always be unique and there are only seven of them. I have tried this:
$('#wrapper').find("input[value='"+value+"']").each(function(){
return this.id;
});
But nothing is returned!
Try
$(this).id nope, this.id works, no need to create a jQuery object for the ID.
or
$(this).attr('id')
HTH
EDIT:
This might work:
$('#wrapper').find("input[value='"+value+"']").attr('id');
You write return this.id;… Return where? You simply return value from anonymous functions and I don't see where you ever trying to use it. So the answer is:
var idYouAreSearchingFor = $('#wrapper').find("input[value='"+value+"']").attr('id');
your code is almost good:
$('#wrapper').find("input[value='"+value+"']").each(function(){
return $(this).attr("id")
});
check here
http://jsfiddle.net/5xsZt/
edit: i have just tested it with this.id it works to. Your code is right. Your error is somewhere else: check it:
http://jsfiddle.net/5xsZt/3/
You can solve this using a filter. Like this:
$('#wrapper input').filter(function() {
return $(this).val() == value;
}).each(function() {
return this.id;
});
Here's a version that will definitely work in all mainstream browsers:
function getInputWithValue(wrapper, value) {
var inputs = wrapper.getElementsByTagName("input");
var i = inputs.length;
while (i--) {
if (inputs[i].value === value) {
return inputs[i];
}
}
return null;
}
var wrapper = document.getElementById("wrapper");
var input = getInputWithValue(wrapper, "some value");
window.alert(input.id);

Categories

Resources