I am currently using knockoutjs with one of my MVC applications.
The Layout template looks like this:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>#ViewBag.Title</title>
#Styles.Render("~/Content/css")
#Scripts.Render("~/bundles/modernizr")
</head>
<body>
<div class="container-fluid head-content">
<div class="row">
<div class="col-xs-6">
<img class="img-responsive" src="~/Images/logo.jpg" />
</div>
<div class="col-xs-3">
<a class="block" href="#" style="display: none" data-bind="visible: showBack, click: goBack">
<div class="block-text">
<h4>Back</h4>
</div>
</a>
</div>
<div class="col-xs-3">
<a class="block" href="#" style="display: none" data-bind="visible: showHome, click: navigateToHome">
<div class="block-text">
<h4>Home</h4>
</div>
</a>
</div>
</div>
</div>
<div class="container-fluid body-content">
#RenderBody()
</div>
#Scripts.Render("~/bundles/jquery")
#Scripts.Render("~/bundles/bootstrap")
#RenderSection("scripts", required: false)
</body>
</html>
and my Index partial looks like this:
#Html.Partial("_Login")
#Html.Partial("_Home")
#Html.Partial("_CutLengths")
#Html.Partial("_MoveStock")
#section scripts {
#Scripts.Render("~/bundles/knockout")
#Scripts.Render("~/bundles/app")
}
My problem is that depending on which page I am on, I would like to use the back button to go to another page. For example, if I am on cutLengths I would want the back button to take me home.
My app.viewmodel.js has a method which looks like this:
// Other operations
self.addViewModel = function (options) {
var viewItem = {},
navigator;
// Add view to AppViewModel.Views enum (for example, app.Views.Home).
self.Views[options.name] = viewItem;
// Add binding member to AppViewModel (for example, app.home);
self[options.bindingMemberName] = ko.computed(function () {
if (self.view() !== viewItem) {
return null;
}
return new options.factory(self, dataModel);
});
if (typeof (options.navigatorFactory) !== "undefined") {
navigator = options.navigatorFactory(self, dataModel);
} else {
navigator = function () {
self.view(viewItem);
};
}
// Add navigation member to AppViewModel (for example, app.NavigateToHome());
self["navigateTo" + options.name] = navigator;
};
What I would like to do is pass a string from the ViewModel I am currently viewing which when the back button is pressed will know to direct me to the right ViewModel.
Is it possible to do this?
I hope I have explained it well, if I haven't please ask and I will try harder :D
You can use either ViewData or ViewBag for passing data from the controller to view. So one option is to just add a few dynamic properties to ViewBag for current view model and prior view model.
ViewData is a dictionary of objects that are stored and retrieved using strings as keys.
ViewBag uses the dynamic feature that was introduced into C# 4.It allows an object to have properties dynamically added to it. I would use this for passing your view model state around.
Neither provide compile time checking, which is the beauty of them, you can add anything you want. With that said it’s always good practice to use strongly typed view models over ViewBag and ViewData.
If you'd rather put something in your view model instead of adding properties to ViewBag, than just add another property in each view model called PreviousViewModel and populate it any time you use the model.
Examples using ViewBag or ViewData
ViewData["LastViewModel"] = "CutLengths";
ViewBag.LastViewModel = "CutLengths";
Access your ViewBag in the Views is no problem, they have global scope. ViewBag is like a global variable that you can attach anything to-- so I'd use them judiciously-- maybe some type of a singleton application manager would be a better design.
Hope this helps
I have solved this now. First I changed my HTML
<div class="col-xs-3">
<a class="block btn-back" href="#" data-bind="visible: showBack, click: goBack"></a>
</div>
<div class="col-xs-3">
<a class="block btn-home" href="#/home" data-bind="visible: showHome"></a>
</div>
Then I edited my app.viewmodel.js file and added these
// Data
self.back = ko.observable(null);
// UI state
self.showBack = ko.observable(true);
self.showHome = ko.observable(true);
self.goBack = function () {
if (self.back()) {
window.location.href = self.back();
} else {
window.history.back();
}
self.back(null); // Reset
};
self.setBackUrl = function (url) {
self.back(url);
}
Then on my addViewModel navigate function, I added this:
if (typeof (options.navigatorFactory) !== "undefined") {
navigator = options.navigatorFactory(self, dataModel);
} else {
navigator = function () { // This is our navigator function which sets the current view
self.showBack(true);
self.showHome(true);
self.error(null); // Reset errors
self.view(viewItem);
};
}
And then in my other view models, I just make a call to setBackUrl like this:
app.setBackUrl("#/cut-lengths");
And if I want to hide my buttons, that is easy too. I just create a navigatorFactory on the viewModel like this:
app.addViewModel({
name: "Home",
bindingMemberName: "home",
factory: HomeViewModel,
navigatorFactory: function (app) {
return function () {
app.showBack(false);
app.showHome(false);
app.error(null);
app.view(app.Views.Home);
}
}
});
Related
<div class="container">
#section HeroSection{
#foreach (var category in Model.AllCategory.Where(m => m.IsFeatured).FirstOrDefault().CategoryPictures.Select(m => m.Picture).Distinct())
{
<div class="hero__item set-bg" data-setbg="/FileStore/images/#category.Url">
<div class="hero__text">
<span>FRUIT FRESH</span>
<h2>Vegetable <br />100% Organic</h2>
<p>Free Pickup and Delivery Available</p>
SHOP NOW
</div>
</div>
}
}
</div>
</div>
</div>
}
</div>
I tried to #Render the //#section HeroSection// as false in _layout page .so that it will only appear in home/index ..not in other pages!
I can think of 2 approaches.
the first one is not so practical which is making 2 different layouts, one without the div and one with it. And you can change the layout of the homepage in the razor file like this
#{Layout = "YourNewLayOut"}
The other approach which I think it's better is to use JavaScript.
you can use window.location.href to get the current url and check if it's the url of the home page you target the div and make hidden otherwise visible.
your function should look like this :
function someFunctionName() {
let div = document.querySelector("#yourDiv");
if (window.location.href === "your home page url ") {
div.style.display = "none";
} else {
div.style.display = "block or flex or what ever you're using";
}
}
and then Invoke this function on loading like this
window.onload = function() {
someFunctionName();
};
I am trying to check the model is null OR not but I am not able to solve the issue.
While rendering the main view I have rendered the partial view as follows
Main View
<div class="modal fade" id="surveyPreviewModal" data-backdrop="static" data-keyboard="false" tabindex="-1" role="dialog" aria-labelledby="surveyPreviewLabel" aria-hidden="true">
<div class="modal-lg modal-dialog">
<div class="modal-content" id="surveyPreviewContent">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">
×
</button>
<h4 class="modal-title" id="surveyPreviewLabel">Survey Preview</h4>
</div>
<div class="modal-body" id="surveyPreviewBody">
#Html.Partial("_surveyPreview")
</div>
</div>
</div>
</div>
and in partial view I have function as below
#model LMS_TraineeSurveyPaginationViewModel
<script type="text/javascript">
function SurveyPreview(){
var surveyQuestionViewModel = #Html.Raw(Json.Serialize(Model.SurveyQuestionsViewModel.ToArray()));
var surveyQuestionOptionChoideViewModel= #Html.Raw(Json.Serialize(Model.SurveyQuestionOptionChoiceViewModel.ToArray()));
$.post('#Url.Action("SurveyPreview", "Survey")', { SurveyID : surveyID,` page : page },
function (data) {
$('#surveyPreviewBody').html('');
$('#surveyPreviewBody').html(data);
SetProgressBar(page,'#(Model==null?0: Model.Pager.TotalPages)');
}).fail(function () {
alert("error in GetTraineeSurvey");
}).success(function () {
});
}
</script>
So while rendering the partial view in this function(SurveyPreview) it is giving the error as model is null and straight away white screen shown. If I haven't called the function which is inside of partial view then why does it checking whether model is null OR not ? it should be whenever I execute function like on button click ?
I have a button on main view from where I am showing the bootstrap modal and on 'show' method of bootstrap modal I am returning the same partial view again to bind the data in ajax call.
Below code is written in partial view
$(document).ready(function () {
$('#surveyPreviewModal').on('show.bs.modal', function (e) {
surveyID = $(e.relatedTarget).attr('data-surveyID');
SurveyPreview(#SurveyPageTypePageNumber.StartPage,null);
});
})
and in controller
public ActionResult SurveyPreview(int SurveyID, int page)
{
------ some code ------
return PartialView("_SurveyPreview",viewModel);
}
Any help on this appreciated !
when you load Partial view using #Html.Partial("_surveyPreview") it required LMS_TraineeSurveyPaginationViewModel to be passed which are not supplied
so to call Partial view you need to write something like
#Html.Partial("_surveyPreview",new LMS_TraineeSurveyPaginationViewModel());
The partial view expects a model of type LMS_TraineeSurveyPaginationViewModel. But you are not passing any model object when rendering partialview from the main view.
In partialview function SurveyPreview() uses Model's properties. Since you are not passing any model object from the main view, Model is coming null in the partial view. That's why you are seeing NullReferenceException.
So you need to make sure that the partial view gets model.
You need to take different approach to render the partial view. You can use Html.Action to call the Action method which will return the partial view and render in the main view.
Replace following line in your main view
#Html.Partial("_surveyPreview")
with
#Html.Action("SurveyPreview", new { SurveyID = "<<somesoveryId>>", page = "<<somepage>>"})
This way I will call SurveyPreview action of the controller with the provided parameters and it will return the partial view with model and it will be rendered.
I am not sure of the what values to be passed in SurveyID and page parameters so I have placed placeholders there. You need to put appropriate values over there.
At the moment we have view name 'Self Deposit' and a controller to back it.
The view is a stand alone page that built with partial views:
Example
Folder name: SelfDeposit
Main View file: _LayoutSelfDeposit.cshtml
Partial Views: Register.cshtml, Pending.cshtml etc.
This is the main view:
<html dir="#(System.Globalization.CultureInfo.CurrentUICulture.TextInfo.IsRightToLeft ? "rtl" : "ltr")">
<head>
<meta name="viewport" content="width=device-width" />
<title>#ViewBag.Title</title>
#Styles.Render("~/Content/SelfDeposit_css")
#Scripts.Render("~/bundles/scripts")
#Scripts.Render("~/bundles/bootstrap")
#Scripts.Render("~/bundles/jqueryval")
#if (System.Globalization.CultureInfo.CurrentUICulture.TextInfo.IsRightToLeft)
{
#Styles.Render("~/Content/BootstapRtl_css")
}
<link href="https://fonts.googleapis.com/css?family=Montserrat:400,700|PT+Sans" rel="stylesheet">
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-12 text-center">
</div>
</div>
</div>
#RenderBody()
<div class="container text-center">
<img src="~/Content/SelfDeposit/footer-icons.png" class="img-responsive footer-icons" />
</div>
#Html.Partial("ThirdPartyPixels/GoogleTagManager")
</body>
</html>
This is the controller that loads the start of it:
private readonly ForexDbContext _Db = DbLoader.GetDb();
// GET: SelfDeposit
public ActionResult Index()
{
return RedirectToAction(nameof(this.Register));
}
public async Task<ActionResult> Register()
{
ViewBag.CountryId = await GetCountiesAsync();
var model = new SelfDepositRegistrationViewModel { };
if (TempData[Main.LEAD_REG_DETAILS_FOR_OPEN_ACCOUNT] != null && TempData[Main.LEAD_REG_DETAILS_FOR_OPEN_ACCOUNT] is LeadRegistered)
{
var leadRegistered = TempData[Main.LEAD_REG_DETAILS_FOR_OPEN_ACCOUNT] as LeadRegistered;
ViewBag.LeadRegisteredDetails = leadRegistered;
model.FirstName = leadRegistered.FirstName;
model.LastName = leadRegistered.LastName;
model.Email = leadRegistered.Email;
model.PhoneNumber = leadRegistered.Phone;
model.PhoneCountryCode = leadRegistered.PhoneCountry;
}
return View(model);
}
What i am trying to achieve is to have multiple Views each one with different layout and css but keep the controller the same and not copy it each time i am adding a view.
I have tried the following: Adding a folder under the main folder:
SelfDeposit-->Layout1-->_LayoutSelfDeposit.cshtml
But it didn't work since i couldn't figure the routing problem.
Does anyone have ideas?
Thanks
To return a different view, you can specify the name of the view you want to return and model as follows:
return View("ViewName", yourModel);
To get the absolute path of the view, use:
return View("~/Views/FolderName/ViewName.cshtml");
Or you can make a partial view and can return like:
return PartialView("PartialViewName", Model);
In my view I have a tag with an ng-bind attribute that is showing the correct boolean value:
<span id="ShowFlag" name="ShowFlag" ng-bind="session.view.showFlag"></span>
When the form is posted on the server side I would like to bind this to a property on the relevant model.
public bool ShowFlag { get; set; }
However, this is always returning false, whereas the value shown in Span tag is showing correctly as true on the page. Is there something obvious I'm missing here?
I think you're something you're missing about how AngularJs binding works. if you want to get a value from the server into an angular model you can use Razor to get that data into JavaScript (the best place is in your Angular controller.)
Here is a quick sample I put together.
This is code from the MVC Controller. In this example we are using Model data and ViewBag data.
public ActionResult Index()
{
dynamic model = new ExpandoObject();
model.ShowFlag = "True";
ViewBag.ShowFlag = "ViewBag True";
return View(model);
}
This is what the view looks like including reference so Angular, JQuery and the code for the AngularJs app and controller:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Demo</title>
</head>
<body>
<div>
<h2>Sample For Stack Overflow</h2>
<div ng-app="glennapp">
<div ng-controller="testController">
<input type="text" ng-model="showFlag" />
<input type="text" ng-model="showFlag2" />
<div>
<span ng-bind="showFlag" ></span>
<span ng-bind="showFlag2" ></span>
</div>
</div>
</div>
</div>
<script type="text/javascript" src="//code.jquery.com/jquery-1.11.3.min.js"></script>
<script type="text/javascript" src="//code.angularjs.org/1.4.8/angular.min.js"></script>
<script type="text/javascript">
var mainApp = angular.module('glennapp', ['glennControllers']);
var glennControllers = angular.module('glennControllers', []);
glennControllers.controller('testController', ['$scope', function ($scope) {
$scope.showFlag = '#ViewBag.ShowFlag';
$scope.showFlag2 = '#Model.ShowFlag';
}]);
</script>
</body>
</html>
Another option would be to create an MVC action that returns JsonResult and then write some JavaScript to make an Ajax call and retrieve the data.
When posting a form only input and select tag values are passed to the server
in you case ShowFlag is a span, so you need to make it an input:
<input type="checkbox" id="ShowFlag" name="ShowFlag" ng-bind="session.view.showFlag"/>
If you are posting to server with ajax, make sure that you serialize your model properly:
for example for the following action:
public ActionResult (FlagsConatiner container)
{
//
}
public class FlagsConatiner
{
public bool ShowFlag { get; set; }
}
Serialized model should look like this:
{
"ShowFlag":"true"
}
As pointed out above, you must use an input for the binding to be successful. I used the following which is now working:
<input type="hidden" id="ShowFlag" name="ShowFlag" ng-value="session.view.showFlag">
I would like some advice on this matter. I have a view page that will display a number of users. One view is to display users in a grid (gallery like) of their images. Second view is to display the same users but by their name in a list layout. I will have a toggle button on the page to switch between the two. What is the best way to go about it? Having two separate view pages or have a partial view of some sort?
Update Code after suggestion below
<div data-bind="template: {name:'grid-template'}"></div>
<div data-bind="template: {name:'list-template'}"></div>
<script style="float:left" type="text/html" id ="grid-template">
<section " style="width:100%; float:left">
<section id="users" data-bind="foreach: Users">
<div id="nameImage">
<figure id="content">
<img width="158" height="158" alt="Gravatar" data-bind="attr:{src: GravatarUrl}"/>
<figcaption>
<a title="Email" id="emailIcon" class="icon-envelope icon-white" data-bind="attr:{'href':'mailto:' + Email()}"></a>
<a title="Profile" id="profileIcon" class="icon-user icon-white"></a>
</figcaption>
</figure>
<p data-bind="text:Name"></p>
</div>
</section>
</section>
</script>
<script style="float:left" type="text/html" id="list-template">
<div data-bind="foreach: Users">
<div style="width:60%; float:left; margin:10px; height:58px">
<img style="float:left; margin-right:5px" width="58" height="58" alt="Gravatar" data-bind="attr:{src: GravatarUrl}"/>
<p style="height:58px; float:left; vertical-align:central" data-bind="text:Name"></p>
<a style="float:right" title="Profile" class="icon-user icon-black"></a>
<a style="float:right" title="Email" class="icon-envelope icon-black" data-bind="attr:{'href':'mailto:' + Email()}"></a>
</div>
</div>
</script>
Knockout Script File
$.views.User.UserViewModel = function (data) {
var self = this;
self.Name = ko.observable(data.Name);
self.Email = ko.observable(data.Email);
self.ContentRole = ko.observable(data.ContentRole);
self.MD5Email = ko.observable(data.MD5Email);
self.GravatarUrl = ko.computed(function () {
return 'http://www.gravatar.com/avatar/' + self.MD5Email() + '?s=300&d=identicon&r=G';
});
self.renderMode = ko.observable('grid');
self.displayTemplate = ko.computed(function () {
return self.renderMode() + '-layout-template';
});
};
Personally, I like having clean isolated small little Partial Views especially if it is going to be regular HTTP POST.
However, based on the assumptions I am making below, I think I can suggest a better implementation design.
My Assumption
You have
Index.cshtml Parent view to display a list Users.
JSON object array containing your list of Users
Based on what I see, you are using KnockoutJS.
Read the KnockoutJS Template Binding especially the "Note 5: Dynamically choosing which template is used" part.
It kind of makes it easier to do what you are doing if you are using KnockoutJS or something similar.
You simply have toggle between the two rendering templates.
<script type="text/html" id="gallery-layout-template"> ... </script>
<script type="text/html" id="listing-layout-template"> ... </script>
<div id="divOutputContainer"
data-bind="template: { name: displayTemplate, foreach: users }"></div>
<script type="text/javascript">
$(document).ready(function() {
// I am just writing out a dummy User array here.
// Render out your User Array JSON encoded using JSON.NET.
var myUsers = [
{ "id" : 1, "name": "User 1" },
{ "id" : 2, "name": "User 2" }
];
// here is your KnockoutJS View Model "class"
function MyKoViewModel(users) {
var self = this;
self.users = ko.observableArray(users);
// Toggle this renderMode observable's value
// between 'listing' and 'gallery' via your Toggle button click event handler
self.renderMode = ko.observable( 'gallery' );
self.displayTemplate = function(user) {
// this will return 'gallery-layout-template' or 'listing-layout-template'
return self.renderMode() + '-layout-template';
}
}
ko.applyBindings( new MyKoViewModel( myUsers ) );
});
</script>
So with that technique, you don't need to make an AJAX call every time to refresh the view with a different rendering template.
You have all your data that you want to display as a client-side JavaScript KnockoutJS view model.
Then, just switch the client-side rendering template using KnockoutJS.
Must more efficient :-)
NOTE
I have a feeling, you might have to use the ko.computed() for the MyKoViewModel's displayTemplate() function like this.
self.displayTemplate = ko.computed(function() {
return self.renderMode() + '-layout-template';
}