I have a server side class:TopicsListModel
with properties as follows:
public class TopicsListModel
{
public TopicsListModel()
{
ChildTopics = new HashSet<TopicsListModel>();
}
public int Id { get; set; }
public string Name { get; set; }
public ICollection<TopicsListModel> ChildTopics { get; set; }
}
There is a function which returns a List,
public JsonResult SearchTopic(String topic)
{
var topics = LandingManager.SearchTopics(topic);
//return Json(topics);
return new JsonResult { Data = topic, JsonRequestBehavior = JsonRequestBehavior.AllowGet };
}
I need to add this to the Backbone model and collection. I am a newbie to Backbone and I am struggling as you can guess. Any help would be greatly appreciated.
I want to build a model which will store data like:
{ name: "Model1", id: 1, submodels: [{ name: "Submodel1", id: 2 }, { name: "Submodel1", id: 2 }] }
I am unable to do so, I am having trouble setting up the basic collection like that, the ASP.NET MVC code part which returns the data I have shared. Sharing whatever I have done in the Backbone:
TestBB = function () {
if (!window.console) window.console = { log: function () { } };
var treeModel = Backbone.Model.extend({});
var treeSubModel = Backbone.Model.extend({});
var treeCollection = Backbone.Collection.extend({
model: treeSubModel,
initialize: function () {
console.log('Collection Initialized');
}
});
var treeView = Backbone.View.extend({
el: $('#tree-view'),
initialize: function () {
// this.collection.bind("render", this.render, this);
// this.collection.bind("addAll", this.addAll, this);
// this.collection.bind("addOne", this.addOne, this);
_.bindAll(this);
},
render: function () {
console.log("render");
console.log(this.collection.length);
$(this.el).html(this.template());
this.addAll();
var template = _.template($("#template").html(), {});
this.el.html(template);
},
addAll: function () {
console.log("addAll");
this.collection.each(this.addOne);
},
addOne: function (model) {
console.log("addOne");
view = new treeView({ model: model });
$("ul", this.el).append(view.render());
},
events: { "click #btnFind": "doFind" },
doFind: function (event) { alert('event fired'); }
});
return {
TreeView: treeView
};} (Backbone);
Please suggest.
It isn't a case of 'binding' the collection to the back-end, as such: you fetch a collection from the server to read, and save (via sync) to write.
Firstly you'll need to let the collection know where the data resides by setting the url property. See: http://backbonejs.org/#Collection-url.
Secondly you'll need to actually retrieve the data using the fetch method. See http://backbonejs.org/#Collection-fetch. Note this is asynchronous, so you'll need to wait for the success callback.
To save data, use the save method on individual models. See http://backbonejs.org/#Model-save.
Solved
I figured out the approach and defined the model and populated it!
Related
From the viewmodel I want to update value to the server straight after it was changed in the view.
class OrderLine
{
itemCode: KnockoutObservable<string>;
itemName: KnockoutObservable<string>;
constructor(code: string, name: string)
{
this.itemCode = ko.observable(code);
this.itemName = ko.observable(code);
//subscribers
this.itemCode.subscribe(this.updateCode, this, "change");
this.itemName.subscribe(this.updateName, this, "change");
}
updateCode = (newvalue: string) =>
{
//Update value to the server
}
updateName = (newvalue: string) =>
{
//Update value to the server
}
}
Both values can be changed by user and with explicit subscriptions updating to the server/database works fine.
In the server side when updating itemCode will update value of itemName too. So response to the client will return json object with new value for itemName
Here i have a problem, because changing value of itemName in the viewmodel will fire a subscription callback method which will update same value to the server again
updateCode = (newvalue: string) =>
{
//Update value to the server
//in the handler of the successful request
this.itemName(updatedvaluefromServer); //-- this trigger another request
}
Question: is it possible to change value of KnockoutObservable which will notify only view subscribers?
Or is it possible to detect that value was changed from the view?
I tried used "sneaky update" by #RPNiemeyer from here https://stackoverflow.com/a/17984353/1565525
but this approach will suspend all subscribers from being notified, including view's subscribers
Here's a pattern that utilizes a computed observable backed by a regular observable:
class OrderLine
{
private _itemCode: KnockoutObservable<string>;
private _itemName: KnockoutObservable<string>;
itemCode: KnockoutComputed<string>;
itemName: KnockoutComputed<string>;
constructor(code: string, name: string)
{
this._itemCode = ko.observable(code);
this._itemName = ko.observable(code);
this.itemCode = ko.computed({
read: () => this._itemCode(),
write: (newValue) => {
this._itemCode(newValue);
// Update server
// In the handler of the successful request:
this._itemName("...");
}
});
this.itemName = ko.computed({
read: () => this._itemName(),
write: (newValue) => {
this._itemName(newValue);
// Update server
}
});
}
}
In the callback on succesful Ajax calls you update the backing observable, not the write in the computed, as to prevent the problem you're having.
Here's what I was talking about. code is a normal observable, and name is a writable computed. When code is updated, the read value of name will be updated. When name is written to, code is updated, which updates the read value of name. Only the update of the read value is a change in the observable value of name, so there are not two updates to name.
If you watch the console, you will see that updating either of the fields generates one update to each of them.
function orderLine(code, name) {
return {
code: code,
name: name
};
}
var serverValues = [
orderLine(1, 'one'),
orderLine(2, 'two'),
orderLine(3, 'three')
];
function getNameFromCode(code) {
var found = ko.utils.arrayFilter(serverValues, function(line) {
return line.code == code;
});
if (found.length == 0) return '';
return found[0].name;
}
function getCodeFromName(name) {
var found = ko.utils.arrayFilter(serverValues, function(line) {
return line.name == name;
});
if (found.length == 0) return '';
return found[0].code;
}
function vm() {
var self = {};
self.code = ko.observable();
self.name = ko.computed({
read: function() {
return getNameFromCode(self.code());
},
write: function(newValue) {
console.debug("Writing code");
self.code(getCodeFromName(newValue));
}
});
self.code.subscribe(function(newValue) {
console.debug("Updating code to:", newValue);
});
self.name.subscribe(function(newValue) {
console.debug("Updating name to:", newValue);
});
return self;
}
ko.applyBindings(vm());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<label>Code</label>
<input data-bind="value:code" />
<br />
<label>Name</label>
<input data-bind="value:name" />
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 the following BreezeController
[BreezeController]
public class BreezeController : ApiController
{
readonly EFContextProvider<MyContext> _ContextProvider = new EFContextProvider<MyContext>();
[HttpGet]
public string Metadata()
{
return _ContextProvider.Metadata();
}
....other controllers exposing model types....
[HttpGet]
public IQueryable<Size> Sizes()
{
return _ContextProvider.Context.Sizes;
}
}
which I access from the client from my DataContext.js with this
var getSizes = function (sizesObservable, modelId) {
var query = entityQuery.from('Sizes').where('ID', '==', modelId)
.orderBy('sortOrder').orderBy('size').orderBy('enteredDate');
return manager.executeQuery(query)
.then(querySucceeded)
.fail(queryFailed);
function querySucceeded(data) {
if (sizesObservable) {
var intialValues = { size: ' Select a Size', sizeID: breeze.core.getUuid(), modelID: modelId };
createNullo(entityNames.size, 'Size', intialValues);
sizesObservable(data.results);
}
log('Retrieved [Sizes] from remote data source', data, false);
}
};
All of this works just fine. I would like to add another route to my controller that has some specialized filtering done on the server.
[HttpGet]
public IQueryable<Size> GetUniqueSizes()
{
return //custom filtering logic here.
}
with the following javascript in my DataContext.js
var getUniqueSizes = function (sizesObservable, modelId) {
var query = entityQuery.from('GetUniqueSizes').where('modelID', '==', modelId).where('Approved', '==', 'True')
.orderBy('sortOrder').orderBy('size').orderBy('enteredDate');
return manager.executeQuery(query)
.then(querySucceeded);
function querySucceeded(data) {
if (sizesObservable) {
var intialValues = { size: ' Select a Size', sizeID: breeze.core.getUuid(), modelID: modelId };
createNullo(entityNames.size, 'Size', intialValues);
sizesObservable(data.results);
}
log('Retrieved [Sizes] from remote data source', data, false);
}
};
but when I do this I get the following error on the client
TypeError: Cannot read property 'toODataFragment' …localhost:63144/scripts/breeze.debug.js:12728:23)
Why is this route not working?
try changing the multiple orderBy statements to a multiple property sort.
.orderBy('sortOrder,size,enteredDate')
likewise, you might have better luck if you combine the where clauses.
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;
}
});
In the following example how would I access the model who triggered the "change:guid" event in the callback function?
Entry = Backbone.Model.extend({
defaults: {
db: 'not specified',
guid: 'not specified'
},
initialize: function ()
{
this.on("change:guid", function (e) {
alert(" guid changed!!!!!");
//access model here
});
}
});
"e" should be the model. According to the documentation the following parameters can be used in the callback:
"change:[attribute]" (model, value, options)
http://backbonejs.org/#Events-catalog
Example:
Entry = Backbone.Model.extend({
defaults: {
db: 'not specified',
guid: 'not specified'
},
initialize: function () {
this.on("change:guid", function (model, value, options) {
console.log(model);
})
this.set('guid', 123);
}
});
var entry = new Entry()
If you take a look at the console, there will be the model.
Try it:
http://jsfiddle.net/r7hXS/