I use a requirejs/crossroads setup.
This is my bootstrapping with some global properties:
ko.applyBindings({
route: router.currentRoute,
user: {
...
},
...
loading: ko.observable(false),
setLoadingState: function(newState) {
this.loading(newState);
}
});
When calling the setLoadingState function from components (passed via params), it tells me that loading is not a function/undefined.
What's the correct way of implementing such mechanisms?
Note that in your (simplified?) example, you don't need an additional method since it only forwards to loading, which can be called directly.
Either use a class like pattern to make sure this refers to your view model like so:
var MyApp = function(router) {
this.route = router.currentRoute,
this.loading = ko.observable(false);
};
MyApp.prototype.setLoadingState = function(newState) {
this.loading(newState);
};
ko.applyBindings(new MyApp(router));
(you can also use the more modern class syntax)
or, use plain objects via a "factory" function:
var MyApp = function(router) {
var route = router.currentRoute,
var loading = ko.observable(false);
var setLoadingState = function(newState) {
loading(newState);
};
// Expose what you want to expose:
return {
loading: loading,
setLoadingState: setLoadingState
};
};
ko.applyBindings(MyApp(router));
Related
Where do you correctly place local functions in vue 2.x?
I could just place them in the "methods" object, but I'd like them to be completely local to the instance if thats possible.
Sort of like this in Plain JS :
window._global = (function () {
function _secretInsideFunct(){
return "FooBar";
}
var __localObject = {
outsideFunct : function () {
return _secretInsideFunct();
}
}
return __localObject;
}());
..where _global._secretInsideFunct() wouldnt be accessible anywhere else but from inside the _global object.
In this specific case I want to make a function that creates an array object if it doesn't exist.. Something like:
function CreateOrSet (workArray, itemName, itemValue ){
var salaryRow = self.Status.Rows.find(r => r.recordID == itemName);
if (!salaryRow) {
salaryRow = { recordID: itemName, recordAmount: 0, recordName: "Løn" };
self.Status.Rows.push(salaryRow);
}
salaryRow.recordAmount = itemValue ;
}
..but a general approach for these cases is better :)
Now this function doesn't looks like a utility or helper function, but relate to a state, Status.Rows. If I were you, I will define it as close as to the state or the module that the state being used.
If the state will be used across the app, maybe I will define it in entry file, index.js or app.vue.
Or If you are using vuex, you can define it as an vuex action. So you may do something like this:
const store = new Vuex.Store({
state: {
status: {
rows: []
},
mutations: {
pushRow (state, salaryRow) {
state.status.rows.push(salaryRow)
},
changeAmount (state, id, amount) {
const salaryRow = state.status.Rows.find(r => r.recordID === id)
salaryRow.recordAmount = amount
}
},
actions: {
createRow (context, itemName) {
const salaryRow = { recordID: itemName, recordAmount: 0, recordName: "Løn" };
context.commit('pushRow', salaryRow)
}
}
})
You can put all of the code to a single action, it is just an idea, how you organize your code depend on your needs.
var app = angular.module('testApp', []);
class Component {
constructor(app, name, template, as, bindings) {
this.bindings = bindings;
this.config = {}
this.config.template = template;
this.config.controllerAs = as;
// pre-create properties
this.config.controller = this.controller;
this.config['bindings'] = this.bindings;
app.component(name, this.config);
console.log("Inside Component ctor()");
}
addBindings(name, bindingType) {
this.bindings[name] = bindingType;
}
controller() {
}
}
class App extends Component {
constructor(app) {
var bindings = {
name: "<"
};
super(app, "app", "Hello", "vm", bindings);
}
controller() {
this.$onInit = () => this.Init(); // DOESN'T WORK
/*
var self = this;
self.$onInit = function () { self.Init(); }; // DOESN'T WORK
*/
/*
this.$onInit = function () { // WORKS
console.log("This works but I don't like it!");
};
*/
}
Init() {
console.log("Init");
}
onNameSelected(user) {
this.selectedUser = user;
}
}
var myApp = new App(app);
<div ng-app="testApp">
<app></app>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.5/angular.js"></script>
I'm trying to "classify" angular 1.5's .component(). I can get most of it figured out but when I try to assign a class method for $onInit it doesn't work. I've tried assigning to it and using arrow notation to call back to the class method but neither work. It does work if I assign an anonymous function directly but I don't want to do that. I want those functions to point to class methods because I find it cleaner.
So ultimately I want my App classes Init() method to get called for $onInit(). Is it possible?
I have one service called wd$cache, that is basically a wrapper for localStorage.setItem and get.item.
Now I'm trying to test a controller that uses that service to achieve a certain result. The main problem is that I have an IF statement that gets triggered only if you have localstorage set already which is driving me nuts! (we are doing TDD here)
SERVICE
(function () {
angular
.module('hub')
.controller('promotionNotificationCtrl', promotionNotificationCtrl);
promotionNotificationCtrl.$inject = [
'hub$promotions',
'hub$client',
'wd$cache'
];
function promotionNotificationCtrl(
hub$promotions,
hub$client,
wd$cache) {
var vm = this;
activate();
//////////
function activate () {
hub$promotions.get(hub$client.brand, hub$client.subbrand).success(function (data) {
if (!wd$cache.get('hub$notification')) {
wd$cache.add('before', 123);
} else {
wd$cache.add('after', 321);
}
});
}
}
})();
TEST
describe('The promotion notification controller', function () {
var controller,
hub$client,
$httpBackend,
wd$cache,
mockData = [{
"foo": "bar"
},
{
"faa": "boo"
}];
beforeEach(module('hub'));
beforeEach(module('wired.core'));
beforeEach(module(function ($provide) {
hub$client = {
brand: 'bw',
subbrand: 'plus'
};
wd$cache = {
add: function () {
},
get: function () {
}
};
$provide.value('hub$client', hub$client);
$provide.value('wd$cache', wd$cache);
spyOn(wd$cache, 'add');
}));
beforeEach(inject(function ($controller, _$httpBackend_, _hub$promotions_) {
controller = $controller('promotionNotificationCtrl');
$httpBackend = _$httpBackend_;
hub$promotions = _hub$promotions_;
// request
$httpBackend.expectGET("/umbraco/api/promotions/get/?brand=bw&lang=en&subbrand=plus").respond(200, mockData);
$httpBackend.flush();
}));
it('should attempt to add a cache with a "before" key if no previous "hub$notification" cache was found', function () {
expect(wd$cache.add).toHaveBeenCalledWith('before', 123); //WORKING
})
it('should attempt to add a cache with a "after" key if a previous "hub$notification" cache was found', function () {
localStorage.setItem('hub$notification');
wd$cache.add('hub$notification');
expect(wd$cache.add).toHaveBeenCalledWith('after', 123); // NOT WORKING
// CANT GET THROUGH THE IF STATEMENT
})
});
Basically I can never get to 'Test Cases' after BeforeEach block, whatever I do. I've tried everything, since mocking it to use actual storage.
Any ideas?
You can provide a mock implementation that is already filled with some data:
var cache = {};
beforeEach(module(function ($provide) {
// ...
wd$cache = {
add: function (key, value) {
cache[key] = value;
},
get: function (key) {
return cache[key];
}
};
// add initial data here or in the individual tests, e.g.
// ...
}));
To set up the cache properly for a specific testcase you can use the cache field like this:
cache['hub$notification'] = 'whatever value makes sense here';
Of course you can also do this in beforeEach.
Currently you are trying to do it like this:
wd$cache.add('hub$notification');
expect(wd$cache.add).toHaveBeenCalledWith('after', 123);
This is problematic for two reasons:
You are not updating the cache because you are spying on the add method without .andCallThrough(). You should fix this (add .andCallThrough() after spy creation) otherwise updates from the controller will not affect the cache.
The spy records your call instead. You don't want this for setup code because it makes subsequent assertions more complicated.
The question:
As I understand in sails.js during initialization process Services are initialized before Models.
Is there any possibility to change this behavior? To make Models load before Services.
If it's not, then how can I load particular settings from the database to use them to build instance of my class described in some Service during this Service initialization?
A little bit code for solidity:
api/models/Model.js
console.log("Model Identified");
module.exports = {
attributes: {
name: { type: 'string', required: true, size: 15 },
//Some extra secret fields
}
};
...
api/services/MyCoolService.js
console.log('service inits');
function MyCoolService(options){
//some extraordinary constructor logic may be ommited
}
MyCoolService.prototype.setOptions = function(options){
//Set values for MyCoolService fields.
}
//Some other methods
var myCoolServiceWithSettingsFromDb = new MyCoolService();
//That's the place
model.findOne(sails.config.myApplication.settingsId).exec(function(err,result){
if(!err)
myCoolServiceWithSettingsFromDb.setOptions(result);
});
module.exports = myCoolServiceWithSettingsFromDb;
It's because you instantiate object in service with constructor that needs sails that not exist. Try use this at MyCoolService;
module.exports = {
someOption: null,
method: function () {
var that = this;
sails.models.model.findOne(sails.config.myApplication.settingsId)
.exec(function (err, result) {
if (!err)
that.someOption = result;
});
}
};
that method can be called by sails.services.mycoolservice.method() or simply MyCoolService.method() to give your service some option from DB.
If you want to initiate them at Sails start, call that method at config/bootstrap.js
Thanks to Andi Nugroho Dirgantara,
I ended up with this solution (I still don't like it much, but it works):
api/services/MyCoolService.js
console.log('service inits');
function MyCoolService(options){
//some extraordinary constructor logic may be ommited
}
//All the same as in question
//The instance
var instance;
module.exports = module.exports = {
init: function(options) {
instance = new MyCoolService(options);
},
get: function() {
return instance;
},
constructor: MyCoolService
};
config/bootstrap.js
...
Model.findOrCreate({ id: 1 }, sails.config.someDefaultSettings).exec(function(err, result) {
if (err)
return sails.log.error(err);
result = result || sails.config.someDefaultSettings;
MyCoolService.init(result);
return sails.log.verbose("MyCoolService Created: ", TbcPaymentProcessorService.get());
});
...
tests/unit/service/MyCoolService.test.js
...
describe('MyCoolService', function() {
it('check MyCoolService', function(done) {
assert.notDeepEqual(MyCoolService.get(), sails.config.someDefaultSettings);
done();
});
});
...
It works: the service is instantiated once while bootstraping and it's instance is avaliable everywhere.
But to me this solution still weird... I still don't understand how to globally instantiate instance of my service (for use in a lot of controllers) and make it the best way.
I have a requestCache: {} object in my router declaration. I have a reviews method mapped to a route with the same name (#reviews). I want to cache the results generated inside this method.
router.js
var AppRouter = Backbone.Router.extend({
currentView: null,
requestCache: {},
reviews: function() {
var self = this;
var reviewCollection = new ReviewCollection();
reviewCollection.url = '/profile/' + this.userid + '/reviews';
if('reviews' in self.requestCache) {
reviewCollection = self.requestCache['reviews'];
self.changeView(new ReviewsView({collection:reviewCollection}), 'reviews');
} else {
reviewCollection.fetch().done(function() {
self.requestCache['reviews'] = reviewCollection;
self.changeView(new ReviewsView({collection:reviewCollection}), 'reviews');
});
}
},
changeView just renders the view using the results.
This works fine. What I want to know is whether this is a good way of caching data?
Take a look at backbone-fetch-cache. It does what you want.
As suggested by SoundCloud team, they've craeted a store object to share models and collections through the code.
I've been using Backbone SingletonModel (https://github.com/reconbot/backbone-singleton)
It works just fine and you can make the same for your collections, defining a getInstance method and a _instance on its static part.
var MyCollection = Backbone.Collection.extend({}, {
_instance: null,
count: 0,
getInstance: function () {
if (!this._instance)
this._instance = new MyCollection();
this.count++;
return this._instance;
}
});