Why are these records not stored in cache? - javascript

I would like to cache my records once they are received, but I can't figure out how. According to the Documentation you can just call this.store.push('model', record), but it doesn't seem to work. Ember requests the data from the server with each call of the route, I would like to do this only once and use the local store after it is fetched from the server.
If I try to debug it as suggested by the Documentation, i get that there is no cache:
Pd.__container__.lookup('store:main').recordCache
// --> undefined
This is my route (where I try to cache it):
Pd.ProductsRoute = Ember.Route.extend({
model: function () {
var promise = this.store.find('product');
var that = this;
promise.then(function(value) {
// Caching supposed to happen here
value.content.forEach(function(product){
that.store.push('product', product);
});
}, function(reason) {
// on rejection
});
return promise;
}
});
And this the according Adapter (seems to work fine):
Pd.ProductAdapter = DS.RESTAdapter.extend({
primaryKey: 'nid', // DOES NOT WORK BUT I CAN LIVE WITH THAT (SEE WORKAROUND)
findAll: function(store, type) {
var url = 'ws/rest/products';
return new Ember.RSVP.Promise(function(resolve, reject) {
jQuery.getJSON(url).then(function(data) {
Ember.Logger.debug("Received Products:"); // TRIGGERS EVERY TIME!
var srcPattern = /src=["']([^'"]+)/;
data.forEach(function(product){
product.id = product.nid;
product.field_image = srcPattern.exec(product.field_image)[1];
});
Ember.Logger.debug(data);
Ember.run(null, resolve, {product: data});
}, function(jqXHR) {
jqXHR.then = null; // tame jQuery's ill mannered promises
Ember.run(null, reject, jqXHR);
});
});
}
});

this.store.find('type') will always make a call to the server for records. If you only want to make a call to the server once do it in the ApplicationRoute and then instead of using find use the all filter inside of the route that's hit multiple times.
Pd.ApplicationRoute = Em.Route.extend({
model: function(params){
return Em.RSVP.hash({
product: this.store.find('product'),
somethingElse: otherPromise
})
}
});
Pd.ProductRoute = Em.Route.extend({
model: function(params){
return this.store.all('product');
}
});
If you just want to prep the store with your products, you don't even need to return it, or use it in the app route
Pd.ApplicationRoute = Em.Route.extend({
model: function(params){
this.store.find('product');
return {foo:'bar'}; // or return nothing, it doesn't matter
}
});
Lazy loading the models
App.ProductRoute = Ember.Route.extend({
hasPreLoaded: false,
model: function() {
if(this.get('hasPreLoaded')){
return this.store.all('product');
} else {
this.toggleProperty('hasPreLoaded');
return this.store.find('product');
}
}
});
Example
http://emberjs.jsbin.com/OxIDiVU/482/edit
You don't define the primary key on the adapter, it goes on the serializer
Pd.ProductSerializer = DS.RESTSerializer.extend({
primaryKey: 'nid'
});
The cache no longer lives there, it lives in this.store.typeMapFor(Pd.Product) or this.store.typeMaps.
The site is still referencing an older version of ember data until ember data 1.0 is released, I'll assume you're using 1.0 beta version. This document is more up to date https://github.com/emberjs/data/blob/master/TRANSITION.md

Related

Ember Understand execution flow between route/controller

I have a "box" route/controller as below;
export default Ember.Controller.extend({
initialized: false,
type: 'P',
status: 'done',
layouts: null,
toggleFltr: null,
gridVals: Ember.computed.alias('model.gridParas'),
gridParas: Ember.computed('myServerPars', function() {
this.set('gridVals.serverParas', this.get('myServerPars'));
this.filterCols();
if (!this.get('initialized')) {
this.toggleProperty('initialized');
} else {
Ember.run.scheduleOnce('afterRender', this, this.refreshBox);
}
return this.get('gridVals');
}),
filterCols: function()
{
this.set('gridVals.layout', this.get('layouts')[this.get('type')]);
},
myServerPars: function() {
// Code to set serverParas
return serverParas;
}.property('type', 'status', 'toggleFltr'),
refreshBox: function(){
// Code to trigger refresh grid
}
});
My route looks like;
export default Ember.Route.extend({
selectedRows: '',
selectedCount: 0,
rawResponse: {},
model: function() {
var compObj = {};
compObj.gridParas = this.get('gridParas');
return compObj;
},
activate: function() {
var self = this;
self.layouts = {};
var someData = {attr1:"I"};
var promise = this.doPost(someData, '/myService1', false); // Sync request (Is there some way I can make this work using "async")
promise.then(function(response) {
// Code to use response & set self.layouts
self.controllerFor(self.routeName).set('layouts', self.layouts);
});
},
gridParas: function() {
var self = this;
var returnObj = {};
returnObj.url = '/myService2';
returnObj.beforeLoadComplete = function(records) {
// Code to use response & set records
return records;
};
return returnObj;
}.property(),
actions: {
}
});
My template looks like
{{my-grid params=this.gridParas elementId='myGrid'}}
My doPost method looks like below;
doPost: function(postData, requestUrl, isAsync){
requestUrl = this.getURL(requestUrl);
isAsync = (isAsync == undefined) ? true : isAsync;
var promise = new Ember.RSVP.Promise(function(resolve, reject) {
return $.ajax({
// settings
}).success(resolve).error(reject);
});
return promise;
}
Given the above setup, I wanted to understand the flow/sequence of execution (i.e. for the different hooks).
I was trying to debug and it kept hopping from one class to another.
Also, 2 specific questions;
I was expecting the "activate" hook to be fired initially, but found out that is not the case. It first executes the "gridParas" hook
i.e. before the "activate" hook. Is it because of "gridParas"
specified in the template ?
When I do this.doPost() for /myService1, it has to be a "sync" request, else the flow of execution changes and I get an error.
Actually I want the code inside filterCols() controller i.e.
this.set('gridVals.layout', this.get('layouts')[this.get('type')]) to
be executed only after the response has been received from
/myService1. However, as of now, I have to use a "sync" request to do
that, otherwise with "async", the execution moves to filterCols() and
since I do not have the response yet, it throws an error.
Just to add, I am using Ember v 2.0
activate() on the route is triggered after the beforeModel, model and afterModel hooks... because those 3 hooks are considered the "validation phase" (which determines if the route will resolve at all). To be clear, this route hook has nothing to do with using gridParas in your template... it has everything to do with callling get('gridParas') within your model hook.
It is not clear to me where doPost() is connected to the rest of your code... however because it is returning a promise object you can tack on a then() which will allow you to essentially wait for the promise response and then use it in the rest of your code.
Simple Example:
this.doPost().then((theResponse) => {
this.doSomethingWith(theResponse);
});
If you can simplify your question to be more clear and concise, i may be able to provide more info
Generally at this level you should explain what you want to archive, and not just ask how it works, because I think you fight a lot against the framework!
But I take this out of your comment.
First, you don't need your doPost method! jQuerys $.ajax returns a thenable, that can be resolved to a Promise with Ember.RSVP.resolve!
Next: If you want to fetch data before actually rendering anything you should do this in the model hook!
I'm not sure if you want to fetch /service1, and then with the response you build a request to /service2, or if you can fetch both services independently and then show your data (your grid?) with the data of both services. So here are both ways:
If you can fetch both services independently do this in your routes model hook:
return Ember.RSVP.hash({
service1: Ember.RSVP.resolve($.ajax(/*your request to /service1 with all data and params, may use query-params!*/).then(data => {
return data; // extract the data you need, may transform the response, etc.
},
service2: Ember.RSVP.resolve($.ajax(/*your request to /service2 with all data and params, may use query-params!*/).then(data => {
return data; // extract the data you need, may transform the response, etc.
},
});
If you need the response of /service1 to fetch /service2 just do this in your model hook:
return Ember.RSVP.resolve($.ajax(/*/service1*/)).then(service1 => {
return Ember.RSVP.resolve($.ajax(/*/service2*/)).then(service2 => {
return {
service1,
service2
}; // this object will then be available as `model` on your controller
});
});
If this does not help you (and I really think this should fix your problems) please describe your Problem.

mock JSON response in Backbone Fetch?

I'm learning Backbone and want to "mock" the results of a .fetch() call within a model. I do not want to use a testing library or actually hit an external service.
Basically I have a setting in my model, where if this.options.mock === true, then just use an internal JSON object as the "result" of the fetch. Else, actually hit the API with a real AJAX request.
However, this doesn't seem to work. My view successfully renders with the model data when I hit the actual API ("real" fetch), but not whenever I try and pass in fake data.
Is there a way to fake a Fetch response in Backbone, without bringing in a testing library like Sinon?
here is the complete model (at least the relevant portions of it). Basically, the model fetches data, and formats it for a template. and then the view which owns the model renders it out.
'use strict';
(function (app, $, Backbone) {
app.Models.contentModel = Backbone.Model.extend({
/**
* Initializes model. Fetches data from API.
* #param {Object} options Configuration settings.
*/
initialize: function (options) {
var that = this;
that.set({
'template': options.template,
'mock': options.mock || false
});
$.when(this.retrieveData()).then(function (data) {
that.formatDataForTemplate(data);
}, function () {
console.error('failed!');
});
},
retrieveData: function () {
var that = this, deferred = $.Deferred();
if (typeof fbs_settings !== 'undefined' && fbs_settings.preview === 'true') {
deferred.resolve(fbs_settings.data);
}
else if (that.get('mock')) {
console.info('in mock block');
var mock = {
'title': 'Test Title',
'description': 'test description',
'position': 1,
'byline': 'Author'
};
deferred.resolve(mock);
}
else {
// hit API like normal.
console.info('in ajax block');
that.fetch({
success: function (collection, response) {
deferred.resolve(response.promotedContent.contentPositions[0]);
},
error: function(collection, response) {
console.error('error: fetch failed for contentModel.');
deferred.resolve();
}
});
}
return deferred.promise();
},
/**
* Formats data on a per-template basis.
* #return {[type]} [description]
*/
formatDataForTemplate: function (data) {
if (this.get('template') === 'welcomead_default') {
this.set({
'title': data.title,
'description': data.description,
'byline': data.author
});
}
// trigger the data formatted event for the view to render.
this.trigger('dataFormatted');
}
});
})(window.app, window.jQuery, window.Backbone);
Relevant bit from the view (ContentView):
this.model = new app.Models.contentModel({template: this.templateName});
this.listenTo(this.model, 'dataFormatted', this.render);
Is the data being set so fast that the listener hasn't been set up yet?
You can override the fetch function like this.
var MockedModel = Backbone.Model.extend({
initialize: function(attr, options) {
if (options.mock) {
this.fetch = this.fakeFetch;
}
},
url: 'http://someUrlThatWIllNeverBeCalled.com',
fakeFetch: function(options) {
var self = this
this.set({
'title': 'Test Title',
'description': 'test description',
'position': 1,
'byline': 'Author'
});
if (typeof options.success === 'function') {
options.success(self, {}, {})
}
}
});
var mockedModel = new MockedModel(null, {
mock: true
})
mockedModel.fetch({
success: function(model, xhr) {
alert(model.get('title'));
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.2/underscore-min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/backbone.js/1.1.2/backbone-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Problem here isn't with the actual implementation of retrieveData but with the way it's being called. When you resolve the deferred before returning you're basically making it instant. This leads to formatDataForTemplate being called while your model is still initializing.
So when you do
this.model = new app.Models.contentModel({template: this.templateName});
this.listenTo(this.model, 'dataFormatted', this.render);
The dataFormatted event ends up being triggered before the listener has registered.
One solution is to use a timeout which should work with just
setTimeout(function() {
deferred.resolve(mock);
});
as that will delay the resolve untill the next round of the event loop when the listener is in place.
Another solution, not involving the setTimeout would be to not call retrieveData during model initialization but rather let the view do it after it has attached its listeners.
this.model = new app.Models.contentModel({template: this.templateName});
this.listenTo(this.model, 'dataFormatted', this.render);
this.model.retrieveData();
I would prefer the latter but if this is just about mocking data to work offline it doesn't really matter in my opinion.
Unrelated to that it's worth noting that the actual signature for initialize on a model is new Model([attributes], [options]) so your initialize should probably look like this
initialize: function (attributes, options) {
var that = this;
that.set({
'template': options.template,
'mock': options.mock || false
});
Just for the sake of readability. That again means that since you are passing only one object you should not need to call set at all.

Ember.js Railscast #410 undefined is not a function TypeError: at RandomRaffle.EntriesRoute.Ember.Route.extend.setupController

I'm following the Ember.js Railscast episode 410. When I change my router.js file from
RandomRaffle.EntriesRoute = Ember.Route.extend({
setupController: function (controller) {
controller.set('content', []);
}
});
to this
RandomRaffle.EntriesRoute = Ember.Route.extend({
setupController: function (controller) {
controller.set('content', RandomRaffle.Entry.find());
}
});
I get the error:
Error while processing route: entries undefined is not a function TypeError: undefined is not a function at RandomRaffle.EntriesRoute.Ember.Route.extend.setupController
My models/entry.js file contains:
RandomRaffle.Entry = DS.Model.extend({
name: DS.attr('string'),
winner: DS.attr('boolean')
});
My controllers/entries_controller.js contains:
RandomRaffle.EntriesController = Ember.ArrayController.extend({
// newEntryName: "",
actions: {
addEntry: function () {
RandomRaffle.Entry.createRecord({name: this.get('newEntryName')});
this.set('newEntryName', "");
},
drawWinner: function () {
this.setEach('highlight', false);
var pool = this.rejectBy('winner');
if (pool.length > 0){
var entry = pool[Math.floor(Math.random() * pool.length)];
entry.set('winner', true);
entry.set('highlight', true);
this.get('store').commit();
}
}
}
});
javascripts/store.js
RandomRaffle.Store = DS.Store.extend({});
// Override the default adapter with the `DS.ActiveModelAdapter` which
// is built to work nicely with the ActiveModel::Serializers gem.
RandomRaffle.ApplicationAdapter = DS.ActiveModelAdapter.extend({});
What am I missing?
Providing your store is properly defined, you should create a model function in your route, similar to this:
RandomRaffle.EntriesRoute = Ember.Route.extend({
model: function() {
return RandomRaffle.Entry.find();
// This is deprecated. Latest versions of ember-data use the following:
// return this.store.find('entry');
// this method returns a promise that will
// carry the model data once it resolves
// This is internally passed as a param of `setupController`
},
setupController: function (controller, model) {
controller.set('content', model);
// this is the same as the default implementation, so if you're *NOT* doing
// anything different than this, get rid of `setupController` altogether
// the `model` param, is the return of the `model` function just above.
// It is returned a `promise` rather than data, and will be made
// available once the promise is resolved, materializing the records
// the param doesn't have to be called `model`, but the
// function above *HAS* to be named `model`
}
});
I am not 100% sure, but I believe the error may be happening because you are calling RandomRaffle.Entry.find() when it should be this.store.find('entry')
The models/entry.js and store.js files are correct in the original question. Plus...
router.js
RandomRaffler.EntriesRoute = Ember.Route.extend({
model: function() {
return this.store.find('entry');
}
});
entries_controller.js
RandomRaffler.EntriesController = Ember.ArrayController.extend({
actions: {
addEntry: function(name) {
//Create the entry record
var entry = this.store.createRecord('entry', {
name: name,
winner: false
});
// Save the changes
entry.save();
},
Also you may notice in your rails server log that there is a problem with the csrf token, this can be fixed by placing this code in the first few lines of the rails controller file (eg entries_controller.rb)
skip_before_action :verify_authenticity_token
A full working app of the Railscast project can be found at https://github.com/markwiggles/raffler

Waiting for models to load before rendering app in Ember.js

I have a number of different application-level models — i.e., current user, current account, etc. — that I want to load before rendering my application. How and where should this be done? This question/answer helped a lot, but it doesn't cover the async aspect.
The following code accomplishes what I want, but loading the models in beforeModel (to take advantage of it waiting for the promise to resolve) doesn't seem right. Should I even be loading these models in ApplicationRoute?
App.ApplicationController = Ember.Controller.extend({
currentAccount: null
});
App.ApplicationRoute = Ember.Route.extend({
beforeModel: function () {
var self = this;
return App.Account.find(...).then(function (account) {
self.controllerFor('application').set('currentAccount', account);
});
}
});
Thanks for your help!
The trick is to return a promise from the route's model method.
This will cause the router to transition into App.LoadingRoute route, until the promise resolves (which can be used for loading indication bars/wheels etc.)
When the promise resolves, the App.LoadingRoute will be deactivated, and the original route's setupController method will be called.
This works for ember-data promises, JQuery's $.ajax promises and ember-model's fetch promises.
Just make sure you return the actual model after resolving the promise.
This can also be a good place to handle errors if the promise is rejected - but I'll leave that to some other question.
As for where you should load your models - that is dependent on your app's usage.
Usually you would load a model where the URL indicates you need that model - a rule of thumb would be the indication of a model ID in the URL.
This of course changes if you need to prefetch some data.
And now for some code:
App.SomeRoute = Ember.Route.extend({
model: function(params){
return App.SomeModel.fetch(params.model_id).then(function(modelData){
// it is better to return the actual model here, and not the promise itself
return App.SomeModel.find(params.model_id);
});
},
setupController: function(controller, model){
controller.set("model", model);
// do some controller setup here - can be omitted if no setup is needed
// this will run only after the promise has been resolved.
}
});
App.LoadingRoute = Ember.Route.extend({
activate: function(){
this._super();
// add some loading indication here
},
deactivate: function(){
this._super();
// remove loading indication
}
}
Hope this helps.
You want to preload data/models to initialize your application, and feel beforeModel is incorrect?
Sounds like you need an application initializer!
Your friend in this instance:
App.deferReadiness(); // halt progress of application until all instances of this call (ie: multiple initializers) are matched by an instance the following call:
App.advanceReadiness(); // consider this to be equivalent to a promise resolve call.
1) From you looking up the user directly, modifying where mentioned to suit your app setup:
Ember.Application.initializer({
name: 'loadUser',
after: 'store',
initialize: function(container, app) {
// modify this following to suit how you're determining the account
var url = 'user/' + currentAccount;
// tell the app to pause loading until advanceReadiness is declared
app.deferReadiness();
// load from JSON
Ember.$.getJSON('url').then(function(json) {
var store = container.lookup('store:main');
store.load(app.User, json);
// tell app to start progressing again
app.advanceReadiness();
});
}
});
2) Through meta tag:
Ember.Application.initializer({
name: 'currentUser'
after: 'store',
initialize: function(container, app) {
app.deferReadiness();
$(function() {
// Look up an attribute in a meta tag
var store = container.lookup('store:main'),
attributes = $('meta[name="current-user"]').attr('content');
if (attributes) {
var obj = store.load(app.User, JSON.parse(attributes)),
user = App.User.find(obj.id),
controller = container.lookup('controller:currentUser').set('content', user);
container.typeInjection('controller', 'currentUser', 'controller:currentUser');
}
app.advanceReadiness();
});
}
});
3) Through Session data:
Ember.Application.initializer({
name : 'currentUser',
after : 'session',
initialize: function(container, app) {
var controller = container.lookup('controller:currentUser');
container.typeInjection('controller', 'currentUser', 'controller:currentUser');
}
});
I managed to get this work by using nested Promises and the afterModel method in the ApplicationRoute.
App.ApplicationRoute = Ember.Route.extend({
model: function() {
// load the reservation (a globally needed model)
return App.Reservation.fetch().then(function(reservations) {
return reservations.get('firstObject');
});
},
afterModel: function() {
// Load all other globally needed models
var self = this;
return App.Gender.fetch().then(function(genders) {
self.controllerFor('application').set('genders', genders);
return App.FilterAttribute.fetch().then(function(filterAttributes) {
self.controllerFor('application').set('filterAttributes', filterAttributes);
//return App.SomeOtherModel...
});
});
},
setupController: function(controller, model) {
controller.set('reservation', model);
}
});
Works just perfectly :-) The application remains in the LoadingRoute until all records are loaded.
Note that I am using Ember Model, but this should make no difference, it just have to return a Promise.

ember.js Uncaught TypeError: Object data-size has no method 'transitionTo'

I am very new to ember and trying to implement authentication via facebook
I am using ember-facebook.js library to connect with facebook. Once the authentication is successful, I want to transition to some other route e.g. '/index'. This library creates a App.FBUser object in mixin which is populated from the facebook response. The blog say following:
Whenever the user changes (login, logout, app authorization, etc) the method updateFBUser is called, updating the App.FBUser object on your application. You can do whatever you want with this binding, observe it, put it in the DOM, whatever.
Ember.Facebook = Ember.Mixin.create({
FBUser: void 0,
appId: void 0,
fetchPicture: true,
init: function() {
this._super();
return window.FBApp = this;
},
appIdChanged: (function() {
var _this = this;
this.removeObserver('appId');
window.fbAsyncInit = function() {
return _this.fbAsyncInit();
};
return $(function() {
var js;
js = document.createElement('script');
$(js).attr({
id: 'facebook-jssdk',
async: true,
src: "//connect.facebook.net/en_US/all.js"
});
return $('head').append(js);
});
}).observes('appId'),
fbAsyncInit: function() {
var _this = this;
FB.init({
appId: this.get('appId'),
status: true,
cookie: true,
xfbml: true
});
this.set('FBloading', true);
FB.Event.subscribe('auth.authResponseChange', function(response) {
return _this.updateFBUser(response);
});
return FB.getLoginStatus(function(response) {
return _this.updateFBUser(response);
});
},
updateFBUser: function(response) {
console.log("Facebook.updateFBUser: Start");
var _this = this;
if (response.status === 'connected') {
//console.log(_this);
return FB.api('/me', function(user) {
var FBUser;
FBUser = user;
FBUser.accessToken = response.authResponse.accessToken;
if (_this.get('fetchPicture')) {
return FB.api('/me/picture', function(path) {
FBUser.picture = path;
_this.set('FBUser', FBUser);
return _this.set('FBloading', false);
});
} else {
_this.set('FBUser', FBUser);
return _this.set('FBloading', false);
}
});
} else {
this.set('FBUser', false);
return this.set('FBloading', false);
}
}//updateFBUser
});
Update :
Adding following observer in my LoginController, I am able to capture the App.FBUser update event(it is update after getting response from FB; as indicated by the blog).
From this observer method, when I try to 'transitionTo' my index route I get following error
Uncaught TypeError: Object data-size has no method 'transitionTo'. Following is the code
App.LoginController = Ember.Controller.extend({
onSuccess: (function(){
var self = this;
/*
//tried all these method to redirect but error is the same
var attemptedTransition = this.get('attemptedTransition');
attemptedTransition.retry();
*/
/*
//tried all these method to redirect but error is the same
var router = this.get('target.router');
router.transitionTo('index');
*/
//tried all these method to redirect but error is the same
this.transitionToRoute('index');
}).observes('App.FBUser')
});
Index Route
App.AuthenticatedRoute = Ember.Route.extend({
beforeModel: function(transition){
var self = this;
if(!App.FBUser){
self.redirectToLogin(transition);
}
},
redirectToLogin: function(transition){
var loginController = this.controllerFor('login');
loginController.set('attemptedTransition', transition);
this.transitionTo('login');
}
});
I am not able to get my head around it.
Any help is greatly appreciated. Thanks
How can I access this object in my Route.beforeModel() hook.
Depending on what route's beforModel hook you are talking about, this is how you could do it:
App.SomeRoute = Ember.Route.extend({
beforeModel: function(transition) {
if (!Ember.isNone(App.FBUser)) {
// calling 'transitionTo' aborts the transition, redirects to 'index'
this.transitionTo('index');
}
}
});
Update in response to your last comment
The addon you are using is slightly outdated and the proposed implementation method for the mixin in your application will not work with the current version of ember:
App = Ember.Application.create(Ember.Facebook)
App.set('appId', 'yourfacebookappid');
starting from version 1.0.0-rc3 of ember you should rather do it like this:
App = Ember.Application.creatWithMixins(Ember.Facebook);
App.set('appId', 'yourfacebookappid');
After that you should be able to have access to the App.FBUser object as mentioned above.
Update 2
If you want to be able to be notified when some events happend, like login, logout etc. you should (as the Author of the addon states on it's blog post) override the updateFBUser method and do in there your transitions.
Since the addon is trough the mixin available in our App namespace you should be able to do the following:
App = Ember.Application.creatWithMixins(Ember.Facebook, {
updateFBUser: function() {
this._super();
// we are calling super to let the addon
// do it's work but at the same time we get
// notified that something happened, so do at this
// point your transition
}
});
Hope it helps.
As per Issue 1 adding
attributeBindings: [],
to:
return Ember.FacebookView = Ember.View.extend({
solved the issue.

Categories

Resources