Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Extending the Prototype

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

From now on, we'll pretty much be adding everything to the prototype key. But, it does get a little bit annoying to always need to say Helper.prototype.something = for every method:

... lines 1 - 2
(function(window, $) {
... lines 4 - 65
Helper.prototype.calculateTotalWeight = function() {
... lines 67 - 72
};
})(window, jQuery);

No worries! We can shorten this with a shortcut that's similar to PHP's array_merge() function. Use $.extend() and pass it Helper.prototype and then a second object containing all of the properties you want to merge into that object. In other words, move our calculateTotalWeight() function into this and update it to be calculateTotalWeight: function:

... lines 1 - 2
(function(window, $) {
... lines 4 - 60
$.extend(Helper.prototype, {
calculateTotalWeight: function() {
var totalWeight = 0;
this.$wrapper.find('tbody tr').each(function () {
totalWeight += $(this).data('weight');
});
return totalWeight;
}
});
})(window, jQuery);

At the bottom, we don't need the semicolon anymore. If we had more properties, we'd add them right below calculateTotalWeight: no need to worry about writing prototype every time.

There's nothing special about $.extend, it's just a handy array_merge-esque function that we happen to have handy. You may see other functions from other libraries that do the same thing.

Making RepLogApp an Instantiatable Object

With this trick, it's super easy to make RepLogApp an instantiatable object. First, set RepLogApp itself to the former initialize() function. I'll un-indent everything and finish it with a semicolon:

... lines 1 - 2
(function(window, $) {
window.RepLogApp = function ($wrapper) {
this.$wrapper = $wrapper;
this.helper = new Helper(this.$wrapper);
this.$wrapper.find('.js-delete-rep-log').on(
'click',
this.handleRepLogDelete.bind(this)
);
this.$wrapper.find('tbody tr').on(
'click',
this.handleRowClick.bind(this)
);
};
... lines 17 - 72
})(window, jQuery);

Constructor done!

Next, add $.extend() with window.RepLogApp.prototype and {. The existing keys fit right into this perfectly! Winning! At the end, add an extra ):

... lines 1 - 2
(function(window, $) {
... lines 4 - 17
$.extend(window.RepLogApp.prototype, {
updateTotalWeightLifted: function () {
this.$wrapper.find('.js-total-weight').html(
this.helper.calculateTotalWeight()
);
},
handleRepLogDelete: function (e) {
e.preventDefault();
var $link = $(e.currentTarget);
$link.addClass('text-danger');
$link.find('.fa')
.removeClass('fa-trash')
.addClass('fa-spinner')
.addClass('fa-spin');
var deleteUrl = $link.data('url');
var $row = $link.closest('tr');
var self = this;
$.ajax({
url: deleteUrl,
method: 'DELETE',
success: function () {
$row.fadeOut('normal', function () {
$(this).remove();
self.updateTotalWeightLifted();
});
}
});
},
handleRowClick: function () {
console.log('row clicked!');
}
});
... lines 55 - 72
})(window, jQuery);

Yes! In our template, we won't use RepLogApp like this anymore. Instead, say var repLogApp = new RepLogApp($table):

... lines 1 - 64
{% block javascripts %}
... lines 66 - 69
<script>
$(document).ready(function() {
var $table = $('.js-rep-log-table');
var repLogApp = new RepLogApp($table);
});
</script>
{% endblock %}

We won't call any methods on that new repLogApp variable, but we could if we wanted to. We could also create multiple RepLogApp objects if we had multiple tables on the page, or if we loaded a table via AJAX. Our JavaScript is starting to be awesome!

Leave a comment!

8
Login or Register to join the conversation
SigmaTechnology Avatar
SigmaTechnology Avatar SigmaTechnology | posted 5 years ago | edited

As touched upon at the end of the video, if you want to instantiate RepLogApp for multiple tables, assuming they've got the same class, you can do so as follows:


$('.js-rep-log-table').each(function(){
 new RepLogApp($(this));
});
1 Reply

I ran into something weird when calling new RepLogApp($wrapper). In my constructor in RepLogApp.js I instantiate the object like window.RepLogApp = function($wrapper) {}; When I instantiate the new RepLogApp($wrapper) object, there is an error in the console: jquery-3.1.1.min.js:2 jQuery.Deferred exception: RepLogApp is not a constructor TypeError: RepLogApp is not a constructor

Which is weird because the RepLogApp() object is setup as a constructor. When I instantiate it as var RepLogApp = new window.RepLogApp($wrapper); The constructor is initiated properly. Is there some detail I'm missing here?

Reply

Just noticed when I call the RepLogApp() constructer outside $(document).ready(); it is initialized without problems:

Reply

Hey sdewijs!

Hmm, that IS weird! So basically, when inside of $(document).ready(), new RepLogApp($wrapper) does not work, but new window.RepLogApp($wrapper) does work, is that correct?

Unless you (somehow) have a local (non global) variable in scope called RepLogApp, RepLogApp and window.RepLogApp should be identical. You could try console.log() each of these to see what each is. The fact that it works outside of the $(document).ready() is another mystery (but probably related): putting code inside this block basically just means its execution is delayed slightly. But, it should not change whether or not some global variable is available.

You could also try copying our version of RepLogApp.js completely (you can get it from the script/code blocks on this page) to see if there is some small bug in your version. I can't think of what would cause this - especially because the self-executing block should prevent any weird variables from "leaking" out. But, as you said, this is a weird situation!

Cheers!

Reply

Yes! Changing the variable name inside document.ready does make a difference.And now it's clear what I did wrong. I wrote var RepLogApp = new RepLogApp($wrapper); with a capital R and in the course code it is var repLogApp = new RepLogApp($wrapper); The devil is in the details as always :)

2 Reply

Nice catch! Really, we don't even need the repLogApp variable - so maybe we should have just done new RepLogApp($wrapper) with no assignment. Glad you figured it out :).

Cheers!

Reply
Default user avatar
Default user avatar jian su | posted 5 years ago

For the $.extend is this jquery method? In JavaScript there is also _.extend also do the same thing?

Reply

Hey Jian,

Yes, jQuery has extend() method, see the docs: https://api.jquery.com/jque... . So $.extend() calls this jQuery method if $ is an object of jQuery of course. JavaScript also has similar method, but it's called extends() (note the "s" at the end) - here's the docs: https://developer.mozilla.o... . But this JS methods is a part of ECMAScript 2015 only.

Cheers!

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

This tutorial uses an older version of Symfony... but since it's a JavaScript tutorial, the concepts are still ? valid!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.2.0",
        "symfony/symfony": "3.1.*", // v3.1.10
        "twig/twig": "2.10.*", // v2.10.0
        "doctrine/orm": "^2.5", // v2.7.1
        "doctrine/doctrine-bundle": "^1.6", // 1.10.3
        "doctrine/doctrine-cache-bundle": "^1.2", // 1.3.2
        "symfony/swiftmailer-bundle": "^2.3", // v2.4.0
        "symfony/monolog-bundle": "^2.8", // 2.12.0
        "symfony/polyfill-apcu": "^1.0", // v1.2.0
        "sensio/distribution-bundle": "^5.0", // v5.0.22
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.16
        "incenteev/composer-parameter-handler": "^2.0", // v2.1.2
        "friendsofsymfony/user-bundle": "~2.0@dev", // dev-master
        "doctrine/doctrine-fixtures-bundle": "~2.3", // v2.4.1
        "doctrine/doctrine-migrations-bundle": "^1.2", // v1.2.1
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "friendsofsymfony/jsrouting-bundle": "^1.6" // 1.6.0
    },
    "require-dev": {
        "sensio/generator-bundle": "^3.0", // v3.1.1
        "symfony/phpunit-bridge": "^3.0" // v3.1.6
    }
}
userVoice