Get a new instance of a durandal viewmodel with every system.acquire - javascript

I'm using the hottowel template and I'm trying to load views dynamically on to a dashboard type view.
The views should also be able to be viewed individually.
So lets say i have a graph view. I want to be able to navigate to it like this :"mysite.com/#/graph/1".
I should also be able to compose the view through ko bindings into the dashboard view.
What I'm doing right now is to on the activate method of the dashboard load a users saved dashboard views. Like this:
dashboard activate
function activate() {
return datacontext.getUserDashboardConfigurations(userId)
.then(function (data) {
_.each(data.elements(), function (view) {
system.acquire("viewmodels/" + view.viewType()).then(function (model) {
var newModel = model.create(); //factory method
newModel.__moduleId__ = model.__moduleId__;
newModel.viewSettingsId(view.SettingsId());
views.push(newModel);
});
});
vm.showLoad(false);
});
}
I probably haven't understood how durandal and/or require correctly. But the model returned by the acquire method is the same for every view of the same type. 3 graph views composed to the dashboard all gets the same instance of the graph model. So what I did was to create a sort of factory method for the model.
Graph model
define( function(logger, dataContext) {
function model() {
var viewSettingsId= ko.observable(0);
function activate() {
//omitted
}
function viewAttached(view) {
//omitted
}
return {
activate: activate,
title: 'Graph View',
viewSettingsId: viewSettingsId
};
}
return {
create: function() {
return new model();
}
};
});
ko binding
<div id="columns" data-bind=" foreach: views">
<div data-bind="compose: { model: $data, activate: true, alwaysAttachView: true, preserveContext: false } "></div>
</div>
router overridden
router.getActivatableInstance = function(routeInfo, params, module) {
if (typeof module == 'function') {
return new module();
} else {
if (module.create && typeof module.create == 'function') {
var newModule = module.create();
newModule.__moduleId__ = module.__moduleId__;
return newModule;
}
return module;
}
};
This sort of solves the problem for the dashboard. Navigation also work. But I feel that there must be a better solution. Maybe durandal widgets? But I want to have them as normal views and "widgets" on the dashboard. How can I solve this in a cleaner way?

I solved it. I found the samples, duh. I have overlooked that link in the documentation.
The master detail sample made it clear. Changed viewmodel to use the prototype pattern in the example. Then use the viewmodel.activateItem after system.acquire.
Then compose the views like the example. Works and feels cleaner!
dashboard activate
function activate() {
return datacontext.getUserDashboardConfigurations(userId)
.then(function (data) {
currentDashboardconfiguration = data;
_.each(data.elements(), function (view) {
system.acquire("viewmodels/" + view.viewType()).then(function (model) {
var newModel = new model();
viewModel.activator().activateItem(newModel, { id: view.viewSettingsId() }).then(function (suc) {
views.push(newModel);
});
});
});
vm.showLoad(false);
});
}
graph model
define( function(logger, dataContext) {
var ctor = function()
{
this.viewSettingsId = ko.observable(0);
this.title: 'Graph View',
}
ctor.prototype.activate = function() {
//omitted
}
ctor.prototype.viewAttached = function(view) {
//omitted
}
return ctor;
});
ko binding
<div id="columns" data-bind=" foreach: views">
<div class="widget column" data-bind="compose: $data"></div>
</div>

Related

Not able to retrieve data in the other controller using common shared service

I'm creating a shared service called Popup Service so that I can share data between two Controllers i.e BankController and CreateBankController.
I'm able to set the object that I need to share in the PopupService. The popupService is called within BankController (while clicking the Edit link on a data row) to set the object to be shared.
The CreateBankController is then invoked by the popup form, but in the CreateBankcontroller I'm not able to access the shared data from the PopupService, I get an error that objectToEdit variable is not defined.
Please can you advise how I can make the PopupService share data between the two controllers
Can the data in the Popup shared service end up over being overridden by competing user actions and show stale data
WORKING PLUNKER
https://plnkr.co/edit/y8dZNU?p=preview
Retrieving data in CreateBankController
CreateBankController.$inject = ['PopupService'];
function CreateBankController(PopupService) {
var vm = this;
var data = {
bankName: "",
bankAddress: "",
};
debugger;
if (PopupService.getObjectToEdit() !== null) {
data = PopupService.getObjectToEdit();
}
SETTING THE SHARED DATA IN THE BankController
$scope.bankGrid = {
dataSource: queryResult,
columns: [{
dataField: 'orderID',
caption: 'Order ID'
}, {
width: 50,
alignment: 'center',
caption: 'Edit',
cellTemplate: function(container, options) {
$('<a/>').addClass('dx-link')
.text('Edit')
.on('dxclick', function() {
PopupService.addObjecToEdit(options.data);
$scope.showPopup = true;
})
.appendTo(container);
}
shared data service - POPUP SERVICE
(function () {
'use strict';
angular
.module('myApp')
.factory('PopupService', PopupService);
function PopupService() {
var popupInstance = {};
var objectToEdit = {};
var service = {
addObjecToEdit : addObjecToEdit,
getObjectToEdit: getObjectToEdit,
showPopup: showPopup,
hidePopup: hidePopup
};
return service;
//Functions
function addObjecToEdit(pObjectToEdit){
objectToEdit = pObjectToEdit;
}
function getObjectToEdit() {
return objecTtoEdit;
}
function showPopup(){
popupInstance.showPopup();
}
function hidePopup(){
popupInstance.hidePopup();
}
}
}());
You have a typo in the service:
function getObjectToEdit() {
return objecTtoEdit;
}
change to:
function getObjectToEdit() {
return objectToEdit;
}

Model method error while trying to navigate

I have several Backbone Models rendered in a Collection View, and also I have a route that should render a view of that model. So, here come the views
resume.js
// this renders a single model for a collection view
var ResumeView = Backbone.View.extend({
model: new Resume(),
initialize: function () {
this.template = _.template($('#resume').html());
},
render: function () {
this.$el.html(this.template(this.model.toJSON));
return this;
}
});
#resume template
<section id="resume">
<h1><%= profession %></h1>
<!-- !!!!! The link for a router which should navigate to ShowResume view -->
View Details
</section>
Collection view:
var ResumeList = Backbone.View.extend({
initialize: function (options) {
this.collection = options.collection;
this.collection.on('add', this.render, this);
// Getting the data from JSON-server
this.collection.fetch({
success: function (res) {
_.each(res.toJSON(), function (item) {
console.log("GET a model with " + item.id);
});
},
error: function () {
console.log("Failed to GET");
}
});
},
render: function () {
var self = this;
this.$el.html('');
_.each(this.collection.toArray(), function (cv) {
self.$el.append((new ResumeView({model: cv})).render().$el);
});
return this;
}
});
The code above works perfectly and does exactly what I need -- an array of models is fetched from my local JSON-server and each model is displayed within a collection view. However, the trouble starts when I try to navigate through my link in the template above. Here comes the router:
var AppRouter = Backbone.Router.extend({
routes: {
'': home,
'resumes/:id': 'showResume'
},
initialize: function (options) {
// layout is set in main.js
this.layout = options.layout
},
home: function () {
this.layout.render(new ResumeList({collection: resumes}));
},
showResume: function (cv) {
this.layout.render(new ShowResume({model: cv}));
}
});
and finally the ShowResume view:
var ShowResume = Backbone.View.extend({
initialize: function (options) {
this.model = options.model;
this.template = _.template($('#full-resume').html());
},
render: function () {
this.$el.html(this.template(this.model.toJSON()));
}
});
I didn't provide the template for this view because it is quite large, but the error is following: whenever I try to navigate to a link, a view tries to render, but returns me the following error: Uncaught TypeError: this.model.toJSON is not a function. I suspect that my showResume method in router is invalid, but I can't actually get how to make it work in right way.
You are passing the string id of the url 'resumes/:id' as the model of the view.
This should solve it.
showResume: function (id) {
this.layout.render(new ShowResume({
model: new Backbone.Model({
id: id,
profession: "teacher" // you can pass data like this
})
}));
}
But you should fetch the data in the controller and react accordingly in the view.
var AppRouter = Backbone.Router.extend({
routes: {
'*otherwise': 'home', // notice the catch all
'resumes/:id': 'showResume'
},
initialize: function(options) {
// layout is set in main.js
this.layout = options.layout
},
home: function() {
this.layout.render(new ResumeList({ collection: resumes }));
},
showResume: function(id) {
// lazily create the view and keep it
if (!this.showResume) {
this.showResume = new ShowResume({ model: new Backbone.Model() });
}
// use the view's model and fetch
this.showResume.model.set('id', id).fetch({
context: this,
success: function(){
this.layout.render(this.showResume);
}
})
}
});
Also, this.model = options.model; is unnecessary as Backbone automatically picks up model, collection, el, id, className, tagName, attributes and events, extending the view with them.

How to separate code: is my controller doing too much work?

Here is a description of the code below:
router decides which controller method to call
controller gets model(s)
controller instantiates various views with model
controller instantiates layout, puts views into it
controller puts layout into app
Is controller doing too many things? I guess the good way should be
router decides which controller method to call
controller gets model(s)
controller instantiates layout with model
controller puts layout into app. End of controller's work
layout when initialized instantiates views with model
Question: Is the second approach better?
If so, how to do [3. and 5. of the good way]?
Code also in jsfiddle
ContactMgr.Router = Marionette.AppRouter.extend({
appRoutes: {
'contacts/:id' : 'detail'
}
});
ContactMgr.Controller = Marionette.Controller.extend({
detail: function (id) {
var promise = App.request('contact:entities', id);
$.when(promise).done( function (contacts) {
var _model = contacts.get(id);
var contactView = new MyContactView({ model: _model });
var sideView = new MySideView({ model: _model });
var view = new MyLayout();
// MyLayout has mainRegion, sideRegion
view.on('show', function (v) {
v.getRegion('mainRegion').show(contactView);
v.getRegion('sideRegion').show(sideView);
});
App.getRegion('contentRegion').show(view);
// App has contentRegion, other regions
});// when done, end
}// detail, end
});
This may be the answer.
And
ContactMgr.Controller = Marionette.Controller.extend({
detail: function (id) {
...
var _model = contacts.get(id);
...
var view = new MyLayout({model: _model});
App.getRegion('contentRegion').show(view);
}
});
MyLayout = Marionette.Layout.extend({
...
regions: {
mainRegion: '#...',
sideRegion: '#...'
},
contactView: null,
sideView: null,
onShow: function () {
this.getRegion('mainRegion').show(this.contactView);
this.getRegion('sideRegion').show(this.sideView);
},
initialize: function (opt) {
var _model = opt.model;
this.contactView = new Marionette.ItemView({ model: _model });
this.sideView = new Marionette.ItemView({ model: _model });
}
});

Marionette best way to do self render

Hello here is my little code :
i don't know how to make this more marionette ... the save function is too much like backbone...
self.model.save(null, {
success: function(){
self.render();
var vFormSuccess = new VFormSuccess();
this.$(".return").html(vFormSuccess.render().$el);
}
var VFormSuccess = Marionette.ItemView.extend({
template: "#form-success"
} );
http://jsfiddle.net/Yazpj/724/
I would be using events to show your success view, as well as using a layout to show your success view, if it's going into a different location.
MyLayout = Marionette.Layout.extend({
template: "#layout-template",
regions: {
form: ".form",
notification: ".return"
}
initialize: function () {
this.listenTo(this.model,'sync',this.showSuccess);
this.form.show(new FormView({model: this.model}));
},
showSuccess: function () {
this.notification.show(new VFormSuccess());
}
});
Or, you could do the same with just the one region, and having the FormView be the layout itself. You just need to ensure there is an element matching the notification region exists in the layout-template.
MyLayout = Marionette.Layout.extend({
template: "#layout-template",
regions: {
notification: ".return"
}
initialize: function () {
this.listenTo(this.model,'sync',this.showSuccess);
},
showSuccess: function () {
this.notification.show(new VFormSuccess());
}
});
What this allows you to do:
You can then show an error view quite easily, if you wanted. You could replace initialize with
initialize: function () {
this.listenTo(this.model,'sync',this.showSuccess);
this.listenTo(this.model,'error',this.showError);
},
and then add the following, ensuring you create a VFormError view.
showError: function () {
this.notification.show(new VFormError());
}
You should be able to write
self.model.save(null, {
success: function(){
self.render();
}
...
Why are you doing this
this.$(".return").html(vFormSuccess.render().$el);
If you define that template as the view template you could simply refer to it with $el, if you need two different templates then you might think about using a Controller, to decide what to use and who to use it.
If you use Marionette, you don't call render directly but instead use Marionette.Region to show your views.

How to pass a model(data) from one view to another in Backbone and edit/delete it?

I have a web application using BackboneJS. In this application, I have a LayoutView.js file in which there is a Backbone View (called LayoutView). LayoutView has other functions (methods) that call other views. I am fetching some data in the initialize function of LayoutView, and I need to get this same data (model) in another view and work (update/delete) on it. Below is how I am passing data from LayoutView to myView:
var LayoutView = Backbone.View.extend({
el: $("#mi-body"),
initialize: function () {
var that = this;
this.ConfigData = new Configurations(); //Configurations is a collection
this.ConfigData.fetch({
success: function () {
alert("success");
},
error: function () {
alert("error");
}
});
this.render();
Session.on('change:auth', function (session) {
var self = that;
that.render();
});
},
render: function () {
// other code
},
events: {
'click #logout': 'logout',
'click #divheadernav .nav li a': 'highlightSelected'
},
myView: function () {
if (Session.get('auth')) {
this.$el.find('#mi-content').html('');
this.options.navigate('Myview');
return new MyLayout(this.ConfigData);
}
}
});
Still, I do not know how to "get"/access this data as my current data/model/collection (I am not sure which term is correct) in myView and work on it using Backbone's "model.save(), model.destroy()" methods. Also, whenever an edit/delete happens, the data of ConfigData should be modified and the update should reflect in the html displayed to the user.
Below is the code from MyView:
var MyView = Backbone.View.extend({
tagName: 'div',
id: "divConfigurationLayout",
initialize: function (attrs) {
this.render();
},
render: function () {
var that = this;
},
events: {
"click #Update": "update",
"click #delete": "delete"
},
update: function(){
//code for updating the data like model.save...
},
delete: function(){
//code for deleting the data like model.destroy...
}
});
Now the data I passed is in attrs in the initialize function. How to get this done..?
The syntax for instantiating a Backbone view is new View(options) where options is an Object with key-value pairs.
To pass a collection to your view, you'd instantiate it like so:
new MyLayout({
collection : this.configData
});
Within your view, this.collection would refer to your configData collection.

Categories