I'm new with backbone and Rails. When I return to the index view in the last line, the index view is not updated with the new value created.
class App.Views.ProfilesIndex extends Backbone.View
template: JST['profiles/index']
initialize: ->
#collection.on('reset', #render, this)
render: ->
$(#el).html(#template(profiles: #collection))
this
And this is my code for the new view
class App.Views.ProfilesNew extends Backbone.View
template: JST['profiles/new']
initialize: ->
#collection = new App.Collections.Profiles()
events: ->
'submit #new_profile': 'createProfile'
render: ->
$(#el).html(#template())
this
createProfile: (event) ->
event.preventDefault()
attributes = name: $('#new_profile_name').val()
#collection.create attributes,
success: -> Backbone.history.navigate("/profiles", {trigger: true})
So, I need to update the collection when the new element is created and returned to index view.
Router
class App.Routers.Profiles extends Backbone.Router
routes:
'profiles': 'index'
'profiles/new': 'new'
initialize: ->
#collection = new App.Collections.Profiles()
#collection.fetch()
index: ->
view = new App.Views.ProfilesIndex(collection: #collection)
$('#container').html(view.render().el)
new: ->
view = new App.Views.ProfilesNew()
$('#container').html(view.render().el)
You have two distinct App.Collections.Profiles collections. Your router has one:
class App.Routers.Profiles extends Backbone.Router
#...
initialize: ->
#collection = new App.Collections.Profiles()
And your ProfilesNew view has its own:
class App.Views.ProfilesNew extends Backbone.View
#...
initialize: ->
#collection = new App.Collections.Profiles()
Your createProfile method adds the new profile to the #collection in the ProfilesNew view and then the router hands its #collection to the ProfilesIndex view:
index: ->
view = new App.Views.ProfilesIndex(collection: #collection)
$('#container').html(view.render().el)
I think you should have just one collection: the one in the router. Then hand that to the ProfilesNew view:
new: ->
view = new App.Views.ProfilesNew(collection: #collection)
$('#container').html(view.render().el)
and remove the initialize method from ProfilesNew. A view's initialize will copy the collection option to #collection for you:
There are several special options that, if passed, will be attached directly to the view: model, collection, el, id, className, tagName and attributes.
Emphasis mine.
Related
I have this link in a View page:
#{
var link = Url.RouteUrl("newOrgNodes", new { controller = "Nodes", Action = "PostBundleNodes", n_qty = 2, org_id = 20 });
Create Nodes
}
In Program.cs I have the following custom route mapping:
app.MapControllerRoute(name: "newOrgNodes",
pattern: "Nodes/PostBundleNodes/{n_qty}/{org_id}",
defaults: new { controller = "Nodes", action = "PostBundleNodes" } );
My Controller has 2 [HttpPost] methods, one of which is:
[HttpPost(), ActionName("PostBundleNodes")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> PostBundleNodes(NodeWriteViewModel newnodes, int n_qty, int org_id)
{
// code here
}
When I hover over the button in the View (parameters hard coded for now) the URL is what I expected it to be but when I click on the button, it loads a blank page instead of hitting the breakpoint in the PostBundleNodes Post method. I thought using [ActionName] in the controller would suffice but no. What am I doing wrong?
I've been playing with MVC3 with KnockoutJs for a few weeks and I've been wondering about something
say I have an mvc action which returns a simple list
public ActionResult AddFoos()
{
List<Foo> funds = new List<Foo>();
.. etc
return Json(funds.ToList(), JsonRequestBehavior.AllowGet);
}
which is then passed into the view model
var viewModel = {
fooChocies: ko.mapping.fromJS([]),
fooOptions: ko.mapping.fromJS([]),
loadInitialData: function () {
ko.mapping.fromJS(serverData, dataMappingOptions, viewModel.fooOptions);
},
};
In my type Foo I also have properties that show or hide ui elements
var Foo = function (data, preselect) {
var self = this;
self.id = ko.observable(data.id);
self.Name = ko.observable(data.Name);
self.number = ko.observable('');
self.showProducts = ko.observable(false); <---
self.displayBigPanel = ko.observable(false); <---
}
My approach so far as been to dynamically create elements of the form
which passes through the ModelBinder and creates a List< Foo > as a parameter for controller action.
Finally the question...
When the user navigates back to this page I need to restore the UI with the fooChoices the user made.
It seems I have 2 choices with rebuilding the user selections (both via extension methods)
Use raw json as seen by
ko.toJSON(viewModel.fooChoices))
which in addition to basic model properties also provides info on hiding and displaying UI elements,
sb.Append("viewModel.fooCghoices= ko.mapping.fromJS(" + json + ");");
sb.Append("ko.applyBindings(viewModel);");
return new HtmlString(sb.ToString());
thus sending client ui info to the server and back
or
Manipulate the ViewModel directly in effect simulating the user actions
sb.Append("viewModel.fooChoices.push(new Foo(1509));");
sb.Append("viewModel.fooChoices()[0].selectedSubFoo = new Foo(273);");
sb.Append("viewModel.fooChoices()[0].showProducts(true);");
In either case it feels a bit off and that a better pattern is out there. Would like to know if one way is better than the other or none of the above.
Many Thanks
Presently, your controller method returns a list of Foo. Consider creating a more complex object that holds both your Foos and your choices.
public class FooViewModel
{
public List<Foo> Foos { get; set; };
public UserChoices { get; set; }
}
Change your controller method so that it returns FooViewModel. This means user choices will be returned along with any Foos you are interested in.
public ActionResult AddFoos()
{
// Are there any choices stored in session?
// Use those first, otherwise create a new UserChoices object
UserChoices choices =
Session["User.Choices"] as UserChoices ?? new UserChoices();
List<Foo> funds = new List<Foo>();
.. etc
FooViewModel vm = new FooViewModel() { Foos = funds; UserChoices = choices };
// Return the whole object, containing Choices and Foos
return Json(vm, JsonRequestBehavior.AllowGet);
}
Also, consider some kind of action filter to allow you to pass complete objects easily. ObjectFilter is a good approach. It allows you to pass complex object structures easily without having to rely on specific markup.
http://www.c-sharpcorner.com/blogs/863/passing-json-into-an-asp-net-mvc-controller.aspx
ObjectFilter above a controller method. Pretty simple, just declaring that the controller should attempt to treat any incoming parameter called fooStuff as type FooViewModel.
[HttpPost,
ObjectFilter(Param = "fooStuff", RootType = typeof(FooViewModel)),
UnitOfWork]
public JsonResult ProcessFoos(FooViewModel fooStuff) {
By defining a corresponding JavaScript view model, you can just convert the whole thing to a json string and pass it to the controller method fully populated.
So, example of corresponding js vm would be:-
var fooViewModel = function(data) {
var self = this;
self.Foos = ko.observableArray(data.Foos);
self.UserChoices = ko.observable(data.UserChoices);
// Don't worry about properties or methods that don't exist
// on the C# end of things. They'll just be ignored.
self.usefulJSOnlyMethod = function() {
// behaviour
};
}
var userChoice = function(data) {
var self = this;
self.DinnerId = ko.observable(data.DinnerId);
}
Typical call to a controller method decorated by ObjectFilter would be something like this ( assuming self is a fooViewModel ):-
var queryData = ko.mapping.toJSON(self);
$.ajax(
//...
data: queryData,
Any matching ( same name, same type case-sensitive ) property from the js vm will automatically end up in the fooStuff parameter of your controller method. Time to save those choices:-
Also note that I'm persisting user choices in the session here. This'll allow them to be picked up by any other controller method which may need them ( example in AddFoos above ).
[HttpPost,
ObjectFilter(Param = "fooStuff", RootType = typeof(FooViewModel)),
UnitOfWork]
public JsonResult ProcessFoos(FooViewModel fooStuff)
{
// hey! I have a fully mapped FooViewModel right here!
// ( _fooServices.ProcessFoos will return updated version of viewmodel )
FooViewModel vm = _fooServices.ProcessFoos(fooStuff);
// What about those choices?
// Put them in the session at this point in case anyone else comes asking
// after them.
Session["User.Choices"] = vm.UserChoices;
return Json(vm);
}
Because we've:-
Defined a better C# view model
Defined a corresponding JS view model
Including UserChoices as part of that view model
....restoring the choice is simple at this point. Reference the part of the view model that contains the user's selected choice.
<select id="dinnerChoice"
data-bind="value: UserChoices.DinnerId"
>
</select>
This is my model:
var AppModel = Backbone.Model.extend({
defaults : {
xmlDeclarationAndDoctype : ''
},
renderFoobar : function () {
this.set({'xmlDeclarationAndDoctype' : 'foobar'});
this.fetchFoobar();
},
fetchFoobar : function () {
console.log(this.get('xmlDeclarationAndDoctype'));
},
fetchAgain : function () {
console.log(this.get('xmlDeclarationAndDoctype'));
}
});
My View:
window.AppView = Backbone.View.extend({
initialize : function () {
model = new AppModel({});
},
render : function () {
model.renderFoobar();
}
});
When I call the following code in my HTML page, I get 'foobar' once the page is rendered.
$(window).load(function () {
var appView = new AppView;
});
In that page, after clicking a button, I'm calling a function which has the following code but this time I get an empty string instead of 'foobar'.
model = new AppModel({});
model.fetchAgain();
Whats wrong in above code?
UPDATES:
mu is too short has given the valid answer, Thanks a ton!. I just would like to add the excerpt out of the correct answer given below.
The new AppModel({}) in AppView#initialize is not the same as the new AppModel({}) somewhere else.
Demo : http://jsfiddle.net/sjagf/2/
The new AppModel({}) that you create in AppView#initialize is not the same as the new AppModel({}) that you create in your button's handler. You're looking at the xmlDeclarationAndDoctype property of two different models and getting two different results.
Calling new AppModel(o), for some set of options o, twice doesn't give you the same object. Backbone doesn't keep track of all the objects that it has created, that's your job or your collection's job. If you look at the cid of your models you'll see:
http://jsfiddle.net/ambiguous/sjagf/
You probably want a collection to help you keep track of your models.
You have more problems. Your view is using a global variable, model:
window.AppView = Backbone.View.extend({
initialize : function () {
model = new AppModel({}); // This is a global
},
and that's probably getting overwritten in your button handler. You should attach model to this:
window.AppView = Backbone.View.extend({
initialize : function () {
this.model = new AppModel({});
},
render : function () {
this.model.renderFoobar();
}
});
Or perhaps like this:
window.AppView = Backbone.View.extend({
render : function () {
this.model.renderFoobar();
}
});
// And elsewhere...
var v = AppView.new({ model: new AppModel({}) });
When I try to bind in an MVC 3 view (using an #Html.DropDownList helper) to a select list based on an IEnumerable< X >, where X is a custom class I created rather than a framework class, I get the error “DataBinding: 'MyCustomNamespace.MyCustomClass' does not contain a property with the name 'MyProperty'.”. I do not get an error if I use a SelectListItem or a KeyValuePair in place of my custom class in the IEnumerable - in that case it works fine. I am guessing that the issue may be that my custom class is not known in the Html.DropDownList helper and hence can’t be accessed there? But I thought this was supposed to operate using reflection and the property names I specified during SelectList definition, so that would not be necessary… ?
Here is a simplified version of my code:
// In .cshtml file:
#Html.DropDownList("cmbSection", (SelectList)ViewBag.Section)
// In Controller:
List<MyCustomClass> filters = new List<MyCustomClass>();
MyCustomClass testItem1 = new MyCustomClass { MyProperty = "AAA"};
MyCustomClass testItem2 = new MyCustomClass { MyProperty = "BBB"};
filters.Add(testItem1);
filters.Add(testItem2);
return new SelectList(filters, "AAA", "MyPropertyName", "MyPropertyName");
// Elsewhere:
public class MyCustomClass
{
public string MyProperty
}
Thanks!
controller
//your code starts
List<MyCustomClass> filters = new List<MyCustomClass>();
MyCustomClass testItem1 = new MyCustomClass { MyProperty = "AAA"};
MyCustomClass testItem2 = new MyCustomClass { MyProperty = "BBB"};
filters.Add(testItem1);
filters.Add(testItem2);
//your code ends here
var items= (from item in filters
select new SelectListItem
{
Value= item.MyProperty
Text= item.MyProperty
}).toList();
ViewBag.items= items;
View
#Html.DropDownList("MyDropDownList", items)
Check out Phil Haacked blog Model Binding To A List. He posts almost everything about how the default model binder works with Lists.
In my controller I do initialization like this:
using mylib.product;
using mylib.factory;
product p = new product();
factory f = new factory(p);
How do I do the same thing using the #model keyword in a partial view?
Thanks
If you are trying to add namespaces/classes to you view, then it's:
#using mylib.product;
I should parse the model to the view by
return View("ViewName");
and in the view;
#model Project.Namespace.Class
You should use view models:
public class MyViewModel
{
public string Name { get; set; }
public string Address { get; set; }
}
which will be passed to the view from the controller action:
public ActionResult Index()
{
product p = new product();
factory f = new factory(p);
var model = new MyViewModel
{
Name = p.Name,
Address = f.Address
}
}
and then your view will be strongly typed to this view model:
#model MyViewModel
#Html.DisplayFor(x => x.Name)
#Html.DisplayFor(x => x.Address)
I think you need to transfer more than one instance of different classes to View.(Am I right?) If yes, I suggest to use ViewBag for it. Something like this:
// Controller
=========
product p = new product();
factory f = new factory(p);
....
// Add some value for p and f
ViewBag.Product = p;
ViewBag.Factory = f;
return View();
// View
=========
var p = (product) ViewBag.Product;
var f = (factory) ViewBag.Factory;
// now you have access to p and f properties, for example:
#Html.Label(p.Name)
#Html.Label(f.Address)
Do not forgot that ViewBag is a Dynamic container and you need to Cast it to a type when you want to use its value in View