I am trying to create a dynamic accordion. My problem is that I can not seem to get a reference to the i variable inside the for loop. I know it is a scope problem but I thought this closure would do the trick.... Please someone help me out as this is driving me completely insane.
jQuery(function(){
var tables = jQuery('table');
var tableHeadings = jQuery('h3');
for(i =0 , ii = tableHeadings.length; i < ii; i++){
(function(){
var index = i;
tables.eq(index).addClass('table-' + index);
tableHeadings.eq(index).click(function(){
tables.eq(index).slideToggle();
});
})();
}
});
Better yet:
tableHeadings.each(function(index, element) {
tables.eq(index).addClass('table-' + index);
tableHeadings.eq(index).click(function() {
tables.eq(index).slideToggle();
});
});
Related
I have this code. For some reason the 1st console.log prints out well in the console but the 2nd gives me an undefined when I click. The cvs array is global.
thanks for the help
var losotro = ['div.santiago', 'div.karina', 'div.roman', 'div.marcos'];
var cvs = ['div#cv0 p', 'div#cv1 p', 'div#cv2 p', 'div#cv3 p'];
for (i = 0; i < losotro.length; i++) {
console.log(cvs[i]);
jQuery(losotro[i]).click(function(){
console.log(cvs[i]);
});
}
This is a typical closure problem in JavaScript.
Basically, all the callback(the click event handlers) are referencing to the same variable i(I know, this is weird to me at first as well), which at the end of the loop should be losotro.length. And
absolutely this is out of the index range of the losotro array.
You may want to check how closure works in JavaScript. But for the current problem, you could do this.
var cvs = ['div#cv0 p', 'div#cv1 p', 'div#cv2 p', 'div#cv3 p'];
for (i = 0; i < losotro.length; i++) {
console.log(cvs[i]);
var bindedFunc = (function(i) {
return function() {
console.log(i)
}
})(i)
jQuery(losotro[i]).click(bindedFunc);
}
In my angularJS application I have a collection (array) of rather large objects. I need to bind to this collection in various places (e.g. to display the property: name of the contained objects) - binding is essential, as these names might change.
As the normal ngRepeat would observe the whole collection by strict equality comparison I am concerned about application speed (we are talking about objects with thousends of properties or more) - I actually just need to observe general changes in the collection (like length, changes of the single references in case two elements are flipped and some specific properties like the mentioned .name property)
I am thinking about using the following approach (basically creating a custom copy of the collection and manually bind to the original collection.
My question:
Is the described approach better than watching the original collection (by equality - as it is my understanding the ngRepeater does) or is there some better approach (e.g. defining some kind of compare callback in a watch statement to check only for changes in certain properties,...)
<script>
function QuickTestController($scope) {
// simulate data from a service
var serviceCollection = [], counter = 0,
generateElement = function() {
var element = { name:'name' + ++counter };
//var element = { name:'name' };
for (var j = 0 ; j < 5 ; j++) element['property' + j] = j;
return element;
};
for (var i = 0 ; i < 5 ; i++) {
serviceCollection.push( generateElement() );
}
// in the view controller we could either bind to the service collection directly (which should internally use a watchCollection and watch every single element for equality)
$scope.viewCollection = serviceCollection;
// watching equality of collection
/*
$scope.$watch('_viewCollectionObserve', function(newValue, oldValue) {
console.log('watch: ', newValue, oldValue);
}, true);
*/
// or we could create our own watchCollection / watch structure and watch only those properties we are interested in
$scope._viewCollectionObserve = serviceCollection;
var viewCollectionManual = [],
rebuildViewCollection = function() {
viewCollectionManual = [];
for (var i = 0, length = serviceCollection.length ; i < length ; i++) {
viewCollectionManual.push( {name:serviceCollection[i].name } );
}
console.log('- rebuildViewCollection - ');
$scope.viewCollection2 = viewCollectionManual;
},
watchCollectionProperties = [],
unregisterWatchCollection = function() {},
rebuildWatchCollectionProperties = function() {
watchCollectionProperties = [];
for (var i = 0, length = serviceCollection.length ; i < length ; i++) {
watchCollectionProperties.push('_viewCollectionObserve[' + i + ']'); // watch for ref changes
watchCollectionProperties.push('_viewCollectionObserve[' + i + '].name'); // watch for changes in specific properties
}
unregisterWatchCollection();
var watchString = '[' + watchCollectionProperties.join(',') + ']';
unregisterWatchCollection = $scope.$watchCollection(watchString, function(newValues, oldValues) {
console.log('watchCollection: ', newValues, oldValues);
rebuildViewCollection();
});
};
$scope.$watch('_viewCollectionObserve.length', function(newValue, oldValue) { // watch add / remove elements to / from collection
console.log('watch / length: ', newValue, oldValue);
rebuildWatchCollectionProperties();
});
// rebuildViewCollection();
rebuildWatchCollectionProperties();
// click handler ---
$scope.changName = function() { serviceCollection[0].name += '1'; };
$scope.changeSomeProperty = function() { serviceCollection[0].property0 += 1; };
$scope.removeElement = function() { serviceCollection.splice(0, 1); };
$scope.addElement = function() { serviceCollection.push( generateElement() ); };
$scope.switchElement = function() {
var temp = serviceCollection[0];
serviceCollection[0] = serviceCollection[1];
serviceCollection[1] = temp;
};
// will of course not react to this (this is desired behaviour!)
$scope.removeCollection = function() { serviceCollection = []; };
}
</script>
<div data-ng-controller="QuickTestController">
<ul>
<li data-ng-repeat="element in viewCollection">{{element.name}} {{element}}</li>
</ul>
<hr>
<ul>
<li data-ng-repeat="element in viewCollection2">{{element.name}} {{element}}</li>
</ul>
<button data-ng-click="changName()">changName</button>
<button data-ng-click="changeSomeProperty()">changeSomeProperty</button>
<button data-ng-click="removeElement()">removeElement</button>
<button data-ng-click="addElement()">addElement</button>
<button data-ng-click="switchElement()">switchElement</button>
<hr>
<button data-ng-click="removeCollection()">removeCollection (see comment)</button>
</div>
Any help / opinions would be greatly appreciated - please note that I tried to create a fiddle to demonstrate my approach but failed :-(
(I know that benchmarking might be a possible solution to test my approach, but I´d rather know the opinion of the angularjs pros in here)
thanks,
matthias
I think what you're looking for is bindonce, which is a high performance binding directive that lets you bind a property or expression once in AngularJS, just as what its name suggests.
One thing you can also try is a 'track by' expression. If you have a property that is unique for each object in the collection, you can pass that to your repeat expression.
<div ng-repeat="item in items track by item.id"></div>
I think Angular will then just watch that property on each of your items. So this should improve performance, but I don't know how much.
I have a problem which I cannot seem to solve. I need to create a function which loops over an array of datasets and creates an independent slickgrids for each dataset. The catch is that the functions need to be bound to each grid independently. For example:
// this part works fine
for(var i=0; i<domain.length; i++){
dataView = new Slick.Data.DataView();
grid = new Slick.Grid('#' + domain[i].name, dataView, domain[i].columns, domain[i].options);
var data = domain[i].data;
// this works well and I am able to create several slickgrid tables
... etc ...
The problem is that every grid is now called "grid". Therefore, when I bind a function like this:
// controls the higlighting of the active row
grid.highlightActiveRow = function () {
var currentCell;
currentCell = this.getActiveCell();
I get a result which affects all grids (or in some cases only one grid).
How do I create multiple, independent grids with associated functions??? The problems seems to be that I have created one object "grid" and then assign all functions using the syntax grid.xxx - but I dont know how to create a unique object on each itteration.
Any help would be most appreciated.
PS: slickgrid is just amazing!!
Thanks
//****** UPDATE *********
#Jokob, #user700284
Thank you both for your help. Here is where I have manged to get to:
var dataView;
function buildTable() {
for(i = 0; i<domains.length; i++){
dataView = new Slick.Data.DataView();
var d = domains[i];
grid = new Slick.Grid('#' + d.name, dataView, d.columns, grids.options);
var data = d.data;
grid.init();
dataView.beginUpdate();
dataView.setItems(data);
// dataView.setFilter(filter); -- will be reinstated once i have this working
dataView.endUpdate();
arrOfGrids.push(grid);
};
};
Jakob - for now i am sticking to "for(i)" until I can wrap my head around your comment - which seems very sensible.
But, using the above, the grid data are not populating. I am not getting any js errors and the column headers are populating but not the data. The reference to d.data is definitely correct as I can see the data using the Chrome js debugger.
Any ideas? Many thanks for your help so far
Instead of assign all new grids to grid (in which case you overwrite the old one everytime you create a new one), push them to an array:
var arrayOfGrids = [];
for(var i=0; i<domain.length; i++) {
dataView = new Slick.Data.DataView();
arrayOfGrids.push(new Slick.Grid('#' + domain[i].name, dataView, domain[i].columns, domain[i].options));
// ....
Then, when you want to something with your grids, like adding the highlight-function, you loop over the array and do it for each element:
for ( var i=0; i<arrayOfGrids.length; i++ ) {
arrayOfGrids[i].highlightActiveRow = function () {
var currentCell;
currentCell = this.getActiveCell();
// ... etc...
BONUS
While we're at it, I would recommend that you use the forEach method that's available on the array-object when iterating over the arrays, rather than the for-loop. The unlike the loop, forEach creates a proper scope for your variables and it gets rid of the useless i-iteration variable:
var arrayOfGrids = [];
domain.forEach(function(d) {
dataView = new Slick.Data.DataView();
arrayOfGrids.push(new Slick.Grid('#' + d.name, dataView, d.columns, d.options));
// ....
And then the same for the other loop of course :)
You could try adding each of the grid instances to an array.You will be able to handle each of the grids differently if you want, by means of <arrray>[<array-index>]
var gridArr = [];
// this part works fine
for(var i=0; i<domain.length; i++){
dataView = new Slick.Data.DataView();
var grid = new Slick.Grid('#' + domain[i].name, dataView, domain[i].columns, domain[i].options);
var data = domain[i].data;
// this works well and I am able to create several slickgrid tables
... etc ...
gridArr.push(grid)
Then if you say gridArr[0] you can access the 1st grid,gridArr[1] second grid and so on.
Just in case anybody else is following this question - here is the working solution:
Many many thanks to #Jokob and #user700284
// default filter function
function filter(item) {
return true; // this is just a placeholder for now
}
var dataView;
function buildTables() {
for(i = 0; i<domains.length; i++){
dataView = new Slick.Data.DataView();
var d = domains[i];
grid = new Slick.Grid('#' + d.name, dataView, d.columns, options);
var data = d.data;
grid.init();
dataView.beginUpdate();
dataView.setItems(data);
dataView.setFilter(filter);
dataView.endUpdate();
grid.invalidate();
grid.render();
arrOfGrids.push(grid);
};
};
I am trying to link to a specific tab in Magento Enterprise. It seems that all of the answers I've found don't apply well to their method. I just need a link to the page to also pull up a specific tab. This is the code they use:
Enterprise.Tabs = Class.create();
Object.extend(Enterprise.Tabs.prototype, {
initialize: function (container) {
this.container = $(container);
this.container.addClassName('tab-list');
this.tabs = this.container.select('dt.tab');
this.activeTab = this.tabs.first();
this.tabs.first().addClassName('first');
this.tabs.last().addClassName('last');
this.onTabClick = this.handleTabClick.bindAsEventListener(this);
for (var i = 0, l = this.tabs.length; i < l; i ++) {
this.tabs[i].observe('click', this.onTabClick);
}
this.select();
},
handleTabClick: function (evt) {
this.activeTab = Event.findElement(evt, 'dt');
this.select();
},
select: function () {
for (var i = 0, l = this.tabs.length; i < l; i ++) {
if (this.tabs[i] == this.activeTab) {
this.tabs[i].addClassName('active');
this.tabs[i].style.zIndex = this.tabs.length + 2;
/*this.tabs[i].next('dd').show();*/
new Effect.Appear (this.tabs[i].next('dd'), { duration:0.5 });
this.tabs[i].parentNode.style.height=this.tabs[i].next('dd').getHeight() + 15 + 'px';
} else {
this.tabs[i].removeClassName('active');
this.tabs[i].style.zIndex = this.tabs.length + 1 - i;
this.tabs[i].next('dd').hide();
}
}
}
});
Anyone have an idea?
I would consider modifying how the class starts up.
initialize: function (container) {
this.container = $(container);
this.container.addClassName('tab-list');
this.tabs = this.container.select('dt.tab');
// change starts here //
var hashTab = $(window.location.hash.slice(1));
this.activeTab = ( this.tabs.include(hashTab) ? hashTab : this.tabs.first());
// change ends here //
this.tabs.first().addClassName('first');
this.tabs.last().addClassName('last');
this.onTabClick = this.handleTabClick.bindAsEventListener(this);
for (var i = 0, l = this.tabs.length; i < l; i ++) {
this.tabs[i].observe('click', this.onTabClick);
}
this.select();
}
Here, I have only changed how the initial tab is chosen. It checks for an URL fragment which is commonly known as a hash, if that identifies one of the tabs it is preselected. As a bonus the browser will also scroll to that element if possible.
Then you only need to append the tab's ID to the URL. For example you might generate the URL by;
$productUrl = Mage::getUrl('catalog/product/view', array(
'id' => $productId,
'_fragment' => 'tab_id',
));
If you've recently migrated from an earlier Magento release, e.g. from Enterprise 1.11 to Enterprise 1.12, make sure the javascript in /template/catalog/product/view.phtml
right after the foreach that generates the tabs gets updated to the 1.12 version:
<script type="text/javascript">
var collateralTabs = new Enterprise.Tabs('collateral-tabs');
Event.observe(window, 'load', function() {
collateralTabs.select();
});
</script>
surfimp's VERY helpful suggestions did not produce the desired opening of the closed tab otherwise. Once this updated javascript was added, clicking on a link to read Review or Add Your Review on the product page, jumped to the Reviews tab, even if the tab had been hidden.
Similar to Zifius' answer, you can modify the initialize function to just take another argument which will be the active tab.
Event.observe(window, 'load', function() {
new Enterprise.Tabs('collateral-tabs', $('tab_review'));
});
and then in the scripts.js (or wherever this class may exist for you)
initialize: function (container, el) {
...
this.activeTab = el;
...
}
Use whatever logic in the template you like to set 'el' to the desired value.
The reason I did it this way is because when I used Zifius' method, the desired tab would be the active tab, but the default tab's content was still displayed.
Had the same task yesterday and as I don't know about prototype much I solved it by adding another method:
selectTab: function (element) {
this.activeTab = element;
this.select();
},
Usage:
var Tabs = new Enterprise.Tabs('collateral-tabs');
Tabs.selectTab($('tabId'));
Would like to know if it's a correct approach
I read some data from a xml file, everything works great besides urls. I can't figure what's the problem with the "navigateURL" function or with the eventListener... on which square I click it opens the last url from the xml file
for(var i:Number = 0; i <= gamesInput.game.length() -1; i++)
{
var square:square_mc = new square_mc();
//xml values
var tGame_name:String = gamesInput.game.name.text()[i];//game name
var tGame_id:Number = gamesInput.children()[i].attributes()[2].toXMLString();//game id
var tGame_thumbnail:String = thumbPath + gamesInput.game.thumbnail.text()[i];//thumb path
var tGame_url:String = gamesInput.game.url.text()[i];//game url
addChild(square);
square.tgname_txt.text = tGame_name;
square.tgurl_txt.text = tGame_url;
//load & attach game thumb
var getThumb:URLRequest = new URLRequest(tGame_thumbnail);
var loadThumb:Loader = new Loader();
loadThumb.load(getThumb);
square.addChild(loadThumb);
//
square.y = squareY;
square.x = squareX;
squareX += square.width + 10;
square.buttonMode = true;
square.addEventListener(MouseEvent.CLICK, navigateURL);
}
function navigateURL(event:MouseEvent):void
{
var url:URLRequest = new URLRequest(tGame_url);
navigateToURL(url, "_blank");
trace(tGame_url);
}
Many thanks!
In navigateURL() you use tGame_url, but I think you'd rather use something like tgurl_txt.text which will be different for each square.
Looks like you're not attaching the event listener properly. Instead of this.addEventListener, attach it to the variable you created when creating new square_mc..... so:
square.addEventListener(MouseEvent.CLICK, navigateURL);
you should add the addEventListener on the Squares
mmm..still figuring how eventhandler function will ever get the correct tgame_url var.
What if you try this:
square.addEventListener(MouseEvent.CLICK, function navigateURL(event:MouseEvent):void
{
var url:URLRequest = new URLRequest(tGame_url);
navigateToURL(url, "_blank");
trace(tGame_url);
});
try tracing this:
function navigateURL(event:MouseEvent):void
{
var url:URLRequest = new URLRequest(tGame_url);
navigateToURL(url, "_blank");
//trace(tGame_url);
trace(event.currentTarget.tgurl_txt.text);
}
you should add the url to your square in the loop
square.theUrl = tGame_url;
in the event listener function you should be able to access it with
event.currentTarget.theUrl;