Let me just preface by saying it's actually my crappy code that's leaking and crashing my browser, I just thought I better make the languages being used as clear as I could from the outset.
I have a test page here and the javascript can be found here. My problem is that when I try and drag and drop either one of the red pieces more than a few times it sucks up all browser resources and crashes the browser. I'm fairly certain the culprit is something in the following function in the Tracker() object but I'm absolutely stuck on how to debug this.
My current most likely culprit:
function register_draggable(ob) {
ob.config.jqId.draggable({cursor: 'move',
grid:[ob.config.size, ob.config.size],
containment: '#chessboard',
revert: 'invalid',
start: function() {
check_allowable_moves(ob.config.jqLocation,
ob.config.jqId,
ob);
},
stop: function() {
remove_allowable_moves();
}
});
}
If anyone could take a quick look and give me any suggestions on what I should be looking for, it would be enormously appreciated.
Solution
Turns out register_draggable() was the culprit. I registered a new draggable every time the location of a piece updated and all those draggables on the same object were doing nasty things.
Currently I now explicity destroy the old draggable before creating a new one. Current code is
function register_draggable(ob) {
ob.config.jqId.draggable('destroy');
ob.config.jqId.draggable({cursor: 'move',
grid:[ob.config.size, ob.config.size],
containment: '#chessboard',
revert: 'invalid',
start: function() {
check_allowable_moves(ob.config.jqLocation,
ob.config.jqId,
ob);
},
stop: function() {
remove_allowable_moves();
}
});
}
I don't think this is actually your problem, but it seems like your making an extra method call on register and check_ allowable_moves
return {
register_map: function(ob) { map = ob; },
register_piece: function(ob) {
ob.config.tracker = this;
register_draggable(ob);
},
register_draggable: function(ob) { register_draggable(ob); },
check_allowable_moves: function(location, jqPiece, ob) { check_allowable_moves(location, jqPiece, ob); }
}
can be shortened to
return {
register_map: function(ob) { map = ob; },
register_piece: function(ob) {
ob.config.tracker = this;
register_draggable(ob);
},
register_draggable: register_draggable,
check_allowable_moves: check_allowable_moves
}
Also
you are doing a double lookup here:
function remove_allowable_moves() {
$('.allowable').droppable('destroy');
$('.allowable').removeClass('allowable');
}
should be
function remove_allowable_moves() {
$('.allowable').droppable('destroy')
.removeClass('allowable');
}
Also
Whats the purpose of parsing and int into a float? Take off the parseFloat.
var x = parseInt(locs[1]);
var y = parseInt(locs[2]);
var x_min = parseFloat(x)-2;
var y_min = parseFloat(y)-2;
Finally
Why are you re-registering as draggable on drop? This could be the culprit, if your registering the draggable multiple times and only destroying it once.
jqCell.droppable({ accept: '#'+jqPiece.attr('id'),
drop: function(ev, ui) {
ob.config.jqLocation = $(this);
register_draggable(ob); // why this?
}
});
Other thoughts
Another thing I don't know if its going to help your performance, but it could clean up your code. the jquery selector allows commas so instead of
$('#coord-1-1').doStuff();
$('#coord-1-2').doStuff();
$('#coord-1-3').doStuff();
you could do
$('#coord-1-1, #coord-1-2, #coord-1-3').doStuff();
so your loop would only be concerned with generating the selector string and then you could run you operation on the entire set.
IMO a cleaner init
instead of
var map = new Map('content');
var piece1 = new Piece(map);
var piece2 = new Piece(map);
var tracker = new Tracker;
tracker.register_map(map);
map.render();
piece1.render('coord-4-4', '1');
piece2.render('coord-1-1', '2');
tracker.register_piece(piece1);
tracker.register_piece(piece2);
I'd like to see
$(document).ready(function() {
$('#content').MapGame({
pieces : { '1' : 'coord-4-4', '2' : 'coord-1-1' }
});
});
Now implementing that is a strech from what you have now, but when building a component for jQuery I like to start with a simple init and work from their. Thats one of the big goals of jQuery is to hide all the junk from the user and just let them spin up and instance of your plugin easily.
It feels like some event-handlers are registered multiple times, but I'm unsure. (Reason below.)
That doesn't answer the question, but you absolutely should put as much code outside of $(document).ready(…) as possible, in no case put all your code in there as you do now.
I fear that your code is so ineligible that it's too much work to understand it. Could you restructure it (All those function-in-function are really horrible to read.) and add some comments.
Maybe it's just me, but I find it too hard to read and understand. It's surely going to be a disaster to maintain.
Related
Please someone help. I'm really broken my brain on this. Neither select text inside modal nor click on input to type text into it.
I think it's because of z-index bugs. But can't find them.
var currentZ = null,
baseZ = 1000,
maxZ = 2000;
Here is an example: http://jsfiddle.net/emvc79sa/1/
This is directive angular-html-window
I'm not sure why the directive author set it this way, but your click actions are consumed by this line:
Window.prototype = {
_resizing: null,
_moving: null,
_restore: null,
events: {
wnd_mousedown: function(event) {
this.focus();
event.preventDefault(); // this line
},
Commenting out this line will let you highlight as normal but I'm not sure if there is any side effects.
I am using jQuery.ProfanityFilter to find swear words in a page. The code is below:
$divs.profanityFilter({
customSwears: ['drunk'],
externalSwears: 'swearWords.json',
filter: false,
profaneText: function (data) {
alert("swear!");
}
});
My question is would there be any way of counting the amount of swear words that are found? I cannot seem to find a suitable way of adapting the code.
Any help would be greatly appreciated.
You may be able to modify your plugin slightly. Based on the link presented by #DontVoteMeDown, you may be able to manually change the profaneText function in the plugin file directly to work with your implementation of it.
In the Plugin File
if (profane) {
options.profaneText(data.unique(), data.length);
};
Then in your own JQuery implementation of the plugin:
$divs.profanityFilter({
customSwears: ['drunk'],
externalSwears: 'swearWords.json',
filter: false,
profaneText: function (data, size) {
console.log("Swear words in element: " + data);
console.log("Number of swear words: " + size);
}
});
Use a global counter variable.
var swears = 0;
$divs.profanityFilter({
customSwears: ['drunk'],
externalSwears: 'swearWords.json',
filter: false,
profaneText: function (data) {
swears++;
alert("swear!");
}
});
I've checked this post, and this one, and this one , and numerous others and none of the solutions seem to help me at all. All I'm trying to do is replace the contents of a view with an array of html. Each element in the array is created by using the underscore templating engine. Here's my code:
The Template:
<script type="text/template" id="radioItemTemplate">
<li>
<div class="button price <% if(enabled){%>enabled<%}%>">$<%=price%></div>
<div class="title name"><%=name%></div>
</li>
</script>
The Javascript:
iHateThisView = Backbone.View.extend({
template: _.template($("#radioItemTemplate").html()),
events:{
"click .price": "_onRadioItemClick"
},
radioItems: null,
radioItem: null,
initialize: function (options) {
this.radioItems = options.radioItems;
this.radioItem = options.radioItem;
},
render: function () {
trace("rendering");
var radioItems = this.radioItems.first(3);
var activeRadioItem = this.radioItem.get('name');
var result = [];
var scope = this;
_.forEach(radioItems, function (radioItem) {
var option = {
name: radioItem.get('name'),
price: radioItem.get('price'),
enabled: activeRadioItem == radioItem.get('name')
};
result.push(scope.template(option));
});
//THE TRICKY ZONE -START
this.$el.html(result);
//THE TRICKY ZONE -END
return this;
},
_onRadioItemClick: function (event) {
$el = this.$el;
var clickedName = $el.find('price');
console.log('clickedName');
}
});
Aside from it wrapping my html with a <div> this does exactly what I want on the first render. However if I called my render function again, none of the events work. So based on all my readings, I figured this.delegateEvents() should fix the loss of events, so I tried this:
//THE TRICKY ZONE -START
this.$el.html(result);
this.delegateEvents();
//THE TRICKY ZONE -END
Which from what I can tell did nothing. On the first render when I click on the radioItems I'd get my console.log, but again not after a re-render
so then I read that I might have to do this:
//THE TRICKY ZONE -START
this.$el.html(result);
this.delegateEvents(this.events);
//THE TRICKY ZONE -END
Which also did nothing.
So then I tried a different method:
//THE TRICKY ZONE -START
this.setElement(result);
this.delegateEvents(); //with and without this line
//THE TRICKY ZONE -END
This added only the first item in the array, and the events didn't work even on the first render.
Please restore my sanity guys, I don't what else to do.
Is there any simple way how to create undo redo function in Kineticjs ?
I have found a Undo Manager for HTML 5 in https://github.com/ArthurClemens/Javascript-Undo-Manager, but I don't know how to put in Kineticjs, please help me.
thank you.
I was able to implement a simple solution based on a post by Chtiwi Malek at CodiCode. I also used some of the code from this problem as an example to draw rectangles, so credits go to them and Chtiwi.
The only difference in my solution is I used toJSON() to store each layer state in an array instead of toDataURL() on the canvas. I think toJSON() is needed over toDataURL() to be able to serialize all the data necessary to store each action on the canvas, but I'm not 100% on this so if someone else knows please leave a comment.
function makeHistory() {
historyStep++;
if (historyStep < history.length) {
history.length = historyStep;
}
json = layer.toJSON();
history.push(json);
}
Call this function everytime you want to save a step to undo or redo. In my case, I call this function on every mouseup event.
Bind these 2 functions to the Undo/Redo events.
function undoHistory() {
if (historyStep > 0) {
historyStep--;
layer.destroy();
layer = Kinetic.Node.create(history[historyStep], 'container')
stage.add(layer);
}
}
function redoHistory() {
if (historyStep < history.length-1) {
historyStep++;
layer.destroy();
layer = Kinetic.Node.create(history[historyStep], 'container')
stage.add(layer);
}
}
Here's the jsfiddle. Don't forget to initialize the array and step counter up top. Good luck!
I am not familiar with KineticJS, but the approach should be similar to the provided demo (that also uses a canvas).
Perhaps another example helps. Let's say I have an app to create/move/delete colored shapes that represent musical notes. I have a way to click-drag and highlight a selection of notes. Pressing Delete on the keyboard invokes the function onDeleteGroup:
onDeleteGroup: function(gridModel) {
// collect all notes in an array
// ...
this._deleteGroup(notes);
this.undoManager.register(
this, this._createGroup, [notes], 'Undo delete',
this, this._deleteGroup, [notes], 'Redo delete'
);
}
All notes are deleted, and 2 methods are registered with the undo manager:
The undo function (undo of delete will be create)
The redo function (after undo/create will be delete again)
Both functions are straightforward:
_deleteGroup:function(notes) {
// removes each note from the model
// thereby removing them from the canvas
// ...
}
_createGroup:function(notes) {
// add each note to the model
// thereby adding them to the canvas
// ...
}
As you can see, the data object (array of notes) is passed around for creation and deleting. You can do the same for manipulating singular objects.
i have written a class for the functionality:
http://www.sebastianviereck.de/en/redo-undo-class-kinetic-js/
To solve event listeners problem, work on by making clones
$scope.makeHistory=function() {
$scope.historyStep++;
if ($scope.historyStep < $scope.history.length) {
$scope.history.length = $scope.historyStep;
}
var layerC = $scope.topLayer.clone();
$scope.history.push(layerC);
};
$scope.undoObject = function(){
if($scope.historyStep > 0) {
$scope.historyStep--;
$scope.topLayer.destroy();
if($scope.historyStep==0){
$scope.topLayerAdd(2); // will put empty layer
}
else{
var layer = $scope.history[$scope.historyStep-1].clone();
$scope.topLayerAdd(1,layer);
}
$scope.topLayer.draw();
}
};
$scope.redoObject = function(){
if($scope.historyStep <= $scope.history.length-1) {
$scope.historyStep++;
$scope.topLayer.destroy();
var layer = $scope.history[$scope.historyStep-1].clone();
if($scope.historyStep==0){
$scope.topLayerAdd(2); // will put empty layer
}
else{
$scope.topLayerAdd(1,layer);
}
$scope.topLayer.draw();
}
};
works perfectly for me.
I have made a simple accordion for my site using jQuery... It worked great, but I've recently started working on a change where if you click the currently opened segments title (the clickable area to slide up/down), it should close the current section.
var sideMenu = {
activated: {},
setup: function() {
$('.menu-category-heading').bind('click', function() {
sideMenu.slideMenu($('ul', $(this).parent()));
});
},
slideMenu: function(menuObj) {
if (sideMenu.activated == menuObj) {
$(sideMenu.activated).slideUp(400);
sideMenu.activated = null;
console.log('same');
} else {
$(sideMenu.activated).slideUp(400);
menuObj.slideDown(500);
sideMenu.activated = menuObj;
console.log('new');
}
}
}
For some reason the comparison is never working... it does if I add $(menuObj).attr('id') and the same for activated. But this is not ideal as not all items will have an id attribute.
Any suggestions as to make the object comparison work? Or any other tips?
Thank you!
You are probably saving a jQuery object (the result of a $ call) rather than the native element. Each time you do a $(myEl) a new object is created and the references will not match up, but the native element will. Try:
if (slideMenu.activated[0] == menuObj[0]) {
...
}