When to call YUI destroy? - memory-leaks

When should destroy be called? Does it ever get called automatically by YUI lifecycle? Does the page unload cause the YUI lifecycle to call destroy on all objects created during the page processing? I have been working under the assumption that I need to make all my own calls to destroy but that gets hairy when ajax calls replace sections of code that I had progressively enhanced. For example:
<div id="replaceMe">
<table>
<tr>
<td>1</td>
</tr>
<tr>
<td>2</td>
</tr>
</table>
<script>
YUI().use('my-lib', function(Y) {
Y.mypackage.enhanceTable("replaceMe");
});
<script>
</div>
The my-lib module basically adds a click handler and mouseover for each row:
YUI.add('my-lib', function(Y) {
function EnhancedTable(config) {
EnhancedTable.superclass.constructor.apply(this, arguments);
}
EnhancedTable.NAME = "enhanced-table";
EnhancedTable.ATTRS = {
containerId : {},
onClickHandler : {},
onMouseoverHandler : {},
onMouseoutHandler : {}
};
Y.extend(EnhancedTable, Y.Base, {
_click : function(e) {
//... submit action
},
destructor : function() {
var onClickHandler = this.get("onClickHandler"),
onMouseoverHandler = this.get("onMouseoverHandler"),
onMouseoutHandler = this.get("onMouseoutHandler");
onClickHandler && onClickHandler.detach();
onMouseoverHandler && onMouseoverHandler.detach();
onMouseoutHandler && onMouseoutHandler.detach();
},
initializer : function(config) {
var container = Y.one("[id=" + this.get("containerId") + "]");
this.set("container", container);
this.set("onMouseoverHandler", container.delegate("mouseover",
this._mouseover, "tr", this ));
this.set("onMouseoutHandler", container.delegate("mouseout",
this._mouseout, "tr", this ));
this.set("onClickHandler", container.delegate("click",
this._click, "tr", this ));
},
_mouseout : function(e) {
e.currentTarget.removeClass("indicated");
},
_mouseover : function(e) {
e.currentTarget.addClass("indicated");
}
});
Y.namespace("mypackage");
Y.mypackage.enhanceTable = function(containerId) {
var enhancedTable new EnhancedTable({containerId:containerId});
};
}, '0.0.1', {
requires : [ 'base', 'node' ]
});
The click handler would submit a request back to my application that would change the page. Do I need to remember all the enhancedTable objects and have an onunload handler call the destroy method of each? Or does the YUI framework take care of this?
The last part of this quesiton is, I also have code outside of this that replaces the whole table by replacing the content of the <div id="replaceMe">. In doing so, the script would get re-run and augment the new <table> with a new EnhancedTable. Do I need to remember the old table, and destroy it before the new table clobbers it?

Instead of setting handlers as attributes I'd store them all in an array like this:
this._handlers = [
container.delegate("mouseover", this._mouseover, "tr", this ),
container.delegate("mouseout", this._mouseout, "tr", this ),
container.delegate("click", this._click, "tr", this )
];
Then add a destructor method that does the following
destructor : function() {
new Y.EventTarget(this._handlers).detach();
}
It accomplishes the same thing but with way less work on your part!
Ideally instead of running this against each table you'd attach all your delegates to #replaceMe so that it wouldn't need to be recreated each time you changed the content, no matter where that happened from.
YUI won't automatically call .destroy() for you on unload, it will clean up DOM subs though. The above is extra credit that's really only necessary if you are going to be destroying the object yourself.

Related

Edit an object in backbone

I am new to using backbone in parse.com environment. I simply want to edit the second model object but I dont know how to open the edit box for the second object.
The current working model is the following, I have added "dblclick label.todo-job" : "edit1" and can get it started by double clicking it.
events: {
"click .toggle" : "toggleDone",
"dblclick label.todo-content" : "edit",
"dblclick label.todo-job" : "edit1",
"click .todo-destroy" : "clear",
"keypress .edit" : "updateOnEnter",
"blur .edit" : "close"
},
The following is the function to allow editing my object.
edit1: function() {
$(this.el).addClass("editing");
this.input.focus();
},
However, it only opens this object "label.todo-content" to edit while I want to edit "label.todo-job". How can I change the focus to the new object.
Thats the whole code if you need.
// The DOM element for a todo item...
var TodoView = Parse.View.extend({
//... is a list tag.
tagName: "li",
// Cache the template function for a single item.
template: _.template($('#item-template').html()),
// The DOM events specific to an item.
events: {
"click .toggle" : "toggleDone",
"dblclick label.todo-content" : "edit",
"dblclick label.todo-job" : "edit1",
"dblclick label.todo-phone" : "edit2",
"dblclick label.todo-email" : "edit3",
"dblclick label.todo-website" : "edit4",
"dblclick label.todo-address" : "edit5",
"click .todo-destroy" : "clear",
"keypress .edit" : "updateOnEnter",
"blur .edit" : "close"
},
// The TodoView listens for changes to its model, re-rendering. Since there's
// a one-to-one correspondence between a Todo and a TodoView in this
// app, we set a direct reference on the model for convenience.
initialize: function() {
_.bindAll(this, 'render', 'close', 'remove');
this.model.bind('change', this.render);
this.model.bind('destroy', this.remove);
},
// Re-render the contents of the todo item.
render: function() {
$(this.el).html(this.template(this.model.toJSON()));
this.input = this.$('.edit');
return this;
},
// Toggle the `"done"` state of the model.
toggleDone: function() {
this.model.toggle();
},
// Switch this view into `"editing"` mode, displaying the input field.
edit: function() {
$(this.el).addClass("editing");
this.input.focus();
},
edit1: function() {
$(this.el).addClass("editing");
this.input.focus();
},
edit2: function() {
$(this.el).addClass("editing");
this.input.focus();
},
edit3: function() {
$(this.el).addClass("editing");
this.input.focus();
},
edit4: function() {
$(this.el).addClass("editing");
this.input.focus();
},
edit5: function() {
$(this.el).addClass("editing");
this.input.focus();
},
// Close the `"editing"` mode, saving changes to the todo.
close: function() {
this.model.save({content: this.input.val()});
$(this.el).removeClass("editing");
},
// If you hit `enter`, we're through editing the item.
updateOnEnter: function(e) {
if (e.keyCode == 13) this.close();
},
// Remove the item, destroy the model.
clear: function() {
this.model.destroy();
}
});
Below is the objects added in the HTML.
<script type="text/template" id="item-template">
<li class="<%= done ? 'completed' : '' %>">
<div class="view">
<li><label class="todo-content"><%= _.escape(content) %></label></li>
<li><label class="todo-job"><%= _.escape(job) %></label></li>
<li><label class="todo-phone"><%= _.escape(phone) %></label></li>
<li><label class="todo-email"><%= _.escape(email) %></label></li>
<li><label class="todo-website"><%= _.escape(web) %></label></li>
<li><label class="todo-address"><%= _.escape(address) %></label></li>
<li><label class="todo-postcode"><%= _.escape(postcode) %></label></li>
<button class="todo-destroy"></button>
</div>
<input class="edit" value="<%= _.escape(content) %>">
<input class="edit" value="<%= _.escape(content) %>"> /*I need to edit this instead of the object above this*/
</li>
</script>
An event triggers on the deepest possible element.which means this of Event handler function is not element you select for event listener but element where the actual event occurs.
I don't know about parse.com though,I assume that label.todo-content is inside of label.todo-job. And that makes Event handler's callback this into label.todo-content.
So If you explicitly select element to focus,It should work.
FYI, Backbone View has $(http://backbonejs.org/#View-dollar) and $el (http://backbonejs.org/#View-$el) parameters to use jQuery methods for elements in side of the View.Since global $ is able to edit any elements over each controller's View area, using this.$ is always recommended.
edit1: function() {
this.$el.addClass("editing");
this.$("label.todo-job").focus();
},
EDITED
I got what you asked about.
I do not know how you wrote your HTML code but the code you provided is pointing first input if your input tags have class name,
edit1: function() {
this.$el.addClass("editing");
this.$(".yourClassNameForInput").focus();
},
or if you do know have class/id name,You can also do this.
edit1: function() {
this.$el.addClass("editing");
this.$("input").eq(0).focus();
},
....
edit5: function() {
this.$el.addClass("editing");
this.$("label.todo-job").eq(4).focus();
}

Unable to load Meteor template, set context and bind events

I am trying to render and append a template (ticket_edit) to the body. I need to set a context to the newly appended template, and the events of ticket_edit should be bound to that template.
The code:
Template.ticket.events = {
'click a.edit' : function (event) {
//when the edit button has been clicked, load template 'ticket_edit'
//with the current context. Please see situation 1 and 2.
}
}
Template.ticket_edit.events = {
'click a.save' : function (event) {
//this won't do anything when i have supplied a context!
}
}
So the problem is:
-I can set the context, but then the events are not bound to the newly added template.
-If I don't set the context the events are bound properly.
But i need both the events and the context.
Situation 1:
'click a.edit' : function (event) {
//applying a context to the template will result in events not being bound.
$('body').append(Meteor.render(Template.ticket_edit(this)));
}
Sitation 2:
'click a.edit' : function (event) {
//this way, the events will execute properly and i can save my ticket.
//However, no context is supplied!
$('body').append(Meteor.render(Template.ticket_edit));
}
Does anybody have a good method for doing this? I'm fairly new to Meteor, so maybe you have a better method of dynamically loading templates.
Don't use jQuery for this, just do it directly with the template and a #with block. Something like this:
<body>
{{> tickets}}
</body>
<template name="tickets">
{{#each tickets}}
{{> ticket}}
{{/each}}
{{#with currentTicket}}
{{> editTicket}}
{{/with}}
</template>
Template.tickets.tickets = function() {
return Tickets.find();
};
Template.tickets.currentTicket = function () {
return Tickets.findOne({ _id: Session.get( "currentTicket" ) });
};
Template.ticket.events({
'click a.edit' : function () {
Session.set( "currentTicket", this._id );
}
});
Because we're using a #with block, the editTicket template won't get rendered until you click the edit button (setting the "currentTicket" in the Session).
It's also possible to just do this (no #with block):
{{> editTicket currentTicket}}
But that causes editTicket to always be rendered, just without any context until the Session var gets set.
Note that because we're using Session, the user won't be interrupted by reloads/hot code pushes.

Does Knockout.mapping make ALL nested objects observable?

I am trying to map all possible nested objects of a JSON object so that each and every one is becomes an observable. I was under the impression that the use of ko.mapping.fromJS would result in all objects and their objects becoming observable. However, I am not seeing that happen.
If you look at the JSFiddle and code below you will see that the span initially displays the value "Test". My intention is for the button click to update the viewModel with the contents of stuff2, which should change the span's value to "Test2". However, the button click does not update anything.
http://jsfiddle.net/Eves/L5sgW/38/
HTML:
<p> <span>Name:</span>
<span data-bind="text: IntroData.Name"></span>
<button id="update" data-bind="click: Update">Update!</button>
</p>
JS:
var ViewModel = function (data) {
var me = this;
ko.mapping.fromJS(data, {}, me);
me.Update = function () {
ko.mapping.fromJS(stuff2, {}, windows.viewModel);
};
return me;
};
var stuff = {
IntroData: {
Name: 'Test'
}
};
var stuff2 = {
IntroData: {
Name: 'Test2'
}
};
window.viewModel = ko.mapping.fromJS(new ViewModel(stuff));
ko.applyBindings(window.viewModel);
Is it just that I have to make use of mapping options to have the nested objects be made observable? If so, what if the JSON object is so vast and complex (this one obviously isn't)? Can some recursive functionality be used to loop through each object's nested objects to make them all observable?
Modifying the Update function as below will work.
me.Update = function () {
ko.mapping.fromJS(stuff2, {}, windows.viewModel);
};

How to put a delay on AngularJS instant search?

I have a performance issue that I can't seem to address. I have an instant search but it's somewhat laggy, since it starts searching on each keyup().
JS:
var App = angular.module('App', []);
App.controller('DisplayController', function($scope, $http) {
$http.get('data.json').then(function(result){
$scope.entries = result.data;
});
});
HTML:
<input id="searchText" type="search" placeholder="live search..." ng-model="searchText" />
<div class="entry" ng-repeat="entry in entries | filter:searchText">
<span>{{entry.content}}</span>
</div>
The JSON data isn't even that large, 300KB only, I think what I need to accomplish is to put a delay of ~1 sec on the search to wait for the user to finish typing, instead of performing the action on each keystroke. AngularJS does this internally, and after reading docs and other topics on here I couldn't find a specific answer.
I would appreciate any pointers on how I can delay the instant search.
UPDATE
Now it's easier than ever (Angular 1.3), just add a debounce option on the model.
<input type="text" ng-model="searchStr" ng-model-options="{debounce: 1000}">
Updated plunker:
http://plnkr.co/edit/4V13gK
Documentation on ngModelOptions:
https://docs.angularjs.org/api/ng/directive/ngModelOptions
Old method:
Here's another method with no dependencies beyond angular itself.
You need set a timeout and compare your current string with the past version, if both are the same then it performs the search.
$scope.$watch('searchStr', function (tmpStr)
{
if (!tmpStr || tmpStr.length == 0)
return 0;
$timeout(function() {
// if searchStr is still the same..
// go ahead and retrieve the data
if (tmpStr === $scope.searchStr)
{
$http.get('//echo.jsontest.com/res/'+ tmpStr).success(function(data) {
// update the textarea
$scope.responseData = data.res;
});
}
}, 1000);
});
and this goes into your view:
<input type="text" data-ng-model="searchStr">
<textarea> {{responseData}} </textarea>
The mandatory plunker:
http://plnkr.co/dAPmwf
(See answer below for a Angular 1.3 solution.)
The issue here is that the search will execute every time the model changes, which is every keyup action on an input.
There would be cleaner ways to do this, but probably the easiest way would be to switch the binding so that you have a $scope property defined inside your Controller on which your filter operates. That way you can control how frequently that $scope variable is updated. Something like this:
JS:
var App = angular.module('App', []);
App.controller('DisplayController', function($scope, $http, $timeout) {
$http.get('data.json').then(function(result){
$scope.entries = result.data;
});
// This is what you will bind the filter to
$scope.filterText = '';
// Instantiate these variables outside the watch
var tempFilterText = '',
filterTextTimeout;
$scope.$watch('searchText', function (val) {
if (filterTextTimeout) $timeout.cancel(filterTextTimeout);
tempFilterText = val;
filterTextTimeout = $timeout(function() {
$scope.filterText = tempFilterText;
}, 250); // delay 250 ms
})
});
HTML:
<input id="searchText" type="search" placeholder="live search..." ng-model="searchText" />
<div class="entry" ng-repeat="entry in entries | filter:filterText">
<span>{{entry.content}}</span>
</div>
In Angular 1.3 I would do this:
HTML:
<input ng-model="msg" ng-model-options="{debounce: 1000}">
Controller:
$scope.$watch('variableName', function(nVal, oVal) {
if (nVal !== oVal) {
myDebouncedFunction();
}
});
Basically you're telling angular to run myDebouncedFunction(), when the the msg scope variable changes. The attribute ng-model-options="{debounce: 1000}" makes sure that msg can only update once a second.
<input type="text"
ng-model ="criteria.searchtext""
ng-model-options="{debounce: {'default': 1000, 'blur': 0}}"
class="form-control"
placeholder="Search" >
Now we can set ng-model-options debounce with time and when blur, model need to be changed immediately otherwise on save it will have older value if delay is not completed.
For those who uses keyup/keydown in the HTML markup.
This doesn't uses watch.
JS
app.controller('SearchCtrl', function ($scope, $http, $timeout) {
var promise = '';
$scope.search = function() {
if(promise){
$timeout.cancel(promise);
}
promise = $timeout(function() {
//ajax call goes here..
},2000);
};
});
HTML
<input type="search" autocomplete="off" ng-model="keywords" ng-keyup="search()" placeholder="Search...">
Debounced / throttled model updates for angularjs : http://jsfiddle.net/lgersman/vPsGb/3/
In your case there is nothing more to do than using the directive in the jsfiddle code like this:
<input
id="searchText"
type="search"
placeholder="live search..."
ng-model="searchText"
ng-ampere-debounce
/>
Its basically a small piece of code consisting of a single angular directive named "ng-ampere-debounce" utilizing http://benalman.com/projects/jquery-throttle-debounce-plugin/ which can be attached to any dom element. The directive reorders the attached event handlers so that it can control when to throttle events.
You can use it for throttling/debouncing
* model angular updates
* angular event handler ng-[event]
* jquery event handlers
Have a look : http://jsfiddle.net/lgersman/vPsGb/3/
The directive will be part of the Orangevolt Ampere framework (https://github.com/lgersman/jquery.orangevolt-ampere).
Just for users redirected here:
As introduced in Angular 1.3 you can use ng-model-options attribute:
<input
id="searchText"
type="search"
placeholder="live search..."
ng-model="searchText"
ng-model-options="{ debounce: 250 }"
/>
I believe that the best way to solve this problem is by using Ben Alman's plugin jQuery throttle / debounce. In my opinion there is no need to delay the events of every single field in your form.
Just wrap your $scope.$watch handling function in $.debounce like this:
$scope.$watch("searchText", $.debounce(1000, function() {
console.log($scope.searchText);
}), true);
Another solution is to add a delay functionality to model update. The simple directive seems to do a trick:
app.directive('delayedModel', function() {
return {
scope: {
model: '=delayedModel'
},
link: function(scope, element, attrs) {
element.val(scope.model);
scope.$watch('model', function(newVal, oldVal) {
if (newVal !== oldVal) {
element.val(scope.model);
}
});
var timeout;
element.on('keyup paste search', function() {
clearTimeout(timeout);
timeout = setTimeout(function() {
scope.model = element[0].value;
element.val(scope.model);
scope.$apply();
}, attrs.delay || 500);
});
}
};
});
Usage:
<input delayed-model="searchText" data-delay="500" id="searchText" type="search" placeholder="live search..." />
So you just use delayed-model in place of ng-model and define desired data-delay.
Demo: http://plnkr.co/edit/OmB4C3jtUD2Wjq5kzTSU?p=preview
I solved this problem with a directive that basicly what it does is to bind the real ng-model on a special attribute which I watch in the directive, then using a debounce service I update my directive attribute, so the user watch on the variable that he bind to debounce-model instead of ng-model.
.directive('debounceDelay', function ($compile, $debounce) {
return {
replace: false,
scope: {
debounceModel: '='
},
link: function (scope, element, attr) {
var delay= attr.debounceDelay;
var applyFunc = function () {
scope.debounceModel = scope.model;
}
scope.model = scope.debounceModel;
scope.$watch('model', function(){
$debounce(applyFunc, delay);
});
attr.$set('ngModel', 'model');
element.removeAttr('debounce-delay'); // so the next $compile won't run it again!
$compile(element)(scope);
}
};
});
Usage:
<input type="text" debounce-delay="1000" debounce-model="search"></input>
And in the controller :
$scope.search = "";
$scope.$watch('search', function (newVal, oldVal) {
if(newVal === oldVal){
return;
}else{ //do something meaningful }
Demo in jsfiddle: http://jsfiddle.net/6K7Kd/37/
the $debounce service can be found here: http://jsfiddle.net/Warspawn/6K7Kd/
Inspired by eventuallyBind directive http://jsfiddle.net/fctZH/12/
Angular 1.3 will have ng-model-options debounce, but until then, you have to use a timer like Josue Ibarra said. However, in his code he launches a timer on every key press. Also, he is using setTimeout, when in Angular one has to use $timeout or use $apply at the end of setTimeout.
Why does everyone wants to use watch? You could also use a function:
var tempArticleSearchTerm;
$scope.lookupArticle = function (val) {
tempArticleSearchTerm = val;
$timeout(function () {
if (val == tempArticleSearchTerm) {
//function you want to execute after 250ms, if the value as changed
}
}, 250);
};
I think the easiest way here is to preload the json or load it once on$dirty and then the filter search will take care of the rest. This'll save you the extra http calls and its much faster with preloaded data. Memory will hurt, but its worth it.

Knockout mapping plugin does not handle hierarchical data properly

I have hierarchical JSON structure that differs after every new JSON from the server side. Given my template, this does not adequately show model update.
After troubleshooting, I noticed the mapping plugin does not correctly map child elements(or perhaps I am doing it incorrectly)
I can also track the memory keeps growing for every update in the datamodel.
Any help will be greatly appreciated.
This simple test is up on JSFiddle http://jsfiddle.net/Bru5a/1/
Here is my view
<div id="view">
The behavior is different depending on the order you load the model.
First
Second
Third
<span data-bind="text: name"></span>
<div data-bind="if: $data.child">
<b data-bind="text: child.name"></b>
<div data-bind="if: child.sub">
<b data-bind="text: child.sub.name"></b>
</div>
</div>
</div>
Here is my Javascript:
var BaseModel = function(om) {
ko.mapping.fromJS(om, {}, this);
};
var resourceModel = null;
function applyJson(json) {
try {
if(resourceModel){
ko.mapping.fromJS(json, {} ,resourceModel);
} else {
resourceModel = new BaseModel(json);
ko.applyBindings(resourceModel, $("#view")[0]);
}
} catch(e) {
alert(e);
}
}
function loadFirst() {
var json = { "name" : "1",
"child" : {
"name": "Child One"
}
};
applyJson(json);
}
function loadSecond() {
var json = { "name" : "2" };
applyJson(json);
}
function loadThird() {
var json = { "name" : "3",
"child" : {
"name": "Child Three",
"sub" : {
"name" : "Third Sub Child"
}
}
};
applyJson(json);
}
$(document).ready(function () {
$("#second").click(function(){
loadSecond();
});
$("#third").click(function(){
loadThird();
});
$("#first").click(function(){
loadFirst();
});
});​
What is happening in your case, is that knockout is behaving as designed.
To see what is happening, add the following line inside your outer div
<div data-bind="text: ko.toJSON($root)"></div>
You can see what is being bound in your view model.
To understand what is being displayed, Watch what is happening the to the viewmodel as you select the different links. As you update your model, you will see that once a property has been created, it remains there. This is by design of how the mapper works.
Next, you have to remember that ko.applyBindings is only being called ONE time. Therefore, the binding are only being applied one time to the properties that are in existence at the time the applyBindings is called. When you add properties to your viewmodel later, they will not automatically be bound to the data-bindings.
To make this example work, you will need to re-think your view model so that all the properties are present at the time you call apply bindings.
EDIT
I've edited your fiddle to show what I was talking about at http://jsfiddle.net/photo_tom/Bru5a/5/

Resources