How to run Angular JS on after page load rendered html? - asp.net-mvc

I'm developing asp.net mvc a project with angular js.
I'm working on tabs and install related partial view after click event.
I am sending with partial view html of the json to main page but angular codes doesn't work on the page
What can i do?
Sample Problem
html:
<div ng-app="MyAppS">
<div ng-controller="AnaTest">
<button id="btn1" ng-click="btn1Click()">click</button>
</div>
<div id="m_area">
</div>
<br />{{ 'Hello Angular' }}</div>
javascript:
var m_app = angular.module('MyAppS', []);
function AnaTest($scope) {
$scope.btn1Click = function () {
var runtimeBtn = angular.element("<button ng-click=\"btn2Click()\">Help Me! </button>");
$('#m_area').html(runtimeBtn);
};
$scope.btn2Click = function(){
debugger;
alert('Why can not show?!');
};
};
m_app.controller('AnaTest', AnaTest);

You need to $compile it:
var runtimeBtn = $compile(angular.element("<button ng-click=\"btn2Click()\">Help Me!</button>"))($scope);
See it here: http://jsfiddle.net/7yqrjdkk/8/
However, a more "Angular" way to do it would be putting it under the same controller/scope and simply using ng-show, like this: http://jsfiddle.net/7yqrjdkk/9/

Related

How to use Vue components (*.vue) in asp.net mvc partial views

I have been trying to get caught up to speed with Vue.js and am working on an asp.net mvc 5 web application that heavily uses jQuery and I would like to start integrating Vue and start replacing jQuery.
I have spent a couple days now trying to integrate Vue into an asp.net mvc 5 web application and with the help of this Best approach when replacing jQuery with VueJS 2 in multi-page existing .NET MVC application, and followed 1_bug's answer.
So in the project that I am hoping to integrate Vue, I am thinking of using components in the partial views first before tackling the the other views.
So in short my question is, how to use Vue components (IE: *.vue files) inside partial views?
I was able to integrate Vue v.2.6 in my ASP.NET Core project without JavaScript bundler using partial views. It should work the same way in a ASP.NET MVC project.
Please check my answer to How do I set up ASP.NET Core + Vue.js? or the sample project on Github for details: ASP.NET Core + Vue.js
I also wrote a step by step description of Using Vue Components in ASP.NET Core at Medium.
This is how I do it. .Vue files are only possible if you use vue-loader (webpack) but as ours is a legacy project, that's not possible
$("vue-category-gallery").each(function () {
new Vue({
el: this,
data: () => ({
action: "",
categoryList: []
}),
beforeMount: function () {
const actionAttribute = this.$el.attributes["data-action"];
if (typeof actionAttribute !== "undefined" && actionAttribute !== null) {
this.action = actionAttribute.value;
} else {
console.error("The data-attribute 'action' is missing for this component.");
}
},
mounted: function () {
if (this.action !== "") {
CategoryService.getGalleryCategoryList(this.action).then(response => {
this.categoryList = response.data;
});
}
},
methods: {
// none
},
template: `
<div class="c-category-gallery" v-if="categoryList.length > 0">
<div class="row">
<div class="col-md-3 col-sm-4" v-for="category in categoryList" :key="category.id">
<div class="c-category-gallery__item">
<img :src="category.imageUrl" :alt="category.name" class="img-responsive"></img>
<div class="c-category-gallery__item-content">
<h4>{{ category.name }}</h4>
<ul class="list-unstyled">
<li v-for="subCategory in category.subCategoryList" :key="subCategory.id">
<a :href="subCategory.categoryUrl" v-if="subCategory.showCategoryUrl">{{ subCategory.name }}</a>
</li>
</ul>
<a :href="category.categoryUrl" v-if="category.showCategoryUrl">{{ category.detailName }}</a>
</div>
</div>
</div>
</div>
</div>`
});
})
Calling it from HTML goes as follows...
<vue-category-gallery
data-action="/api/categories/getcategorygallerylist?itemCount=4"
v-cloak></vue-category-gallery>
I am currently looking into Vue.component("", ...) but that's still in testing :)
EDIT:
With Vue.component("", ...)
Vue.component("vue-person", {
props: ["name"],
data: function () {
return {
type: "none",
age: 0
}
},
template: `<div>
<p>My name is {{name}}.</p>
<p>I am {{type}} and age {{age}}.</p>
</div>`
})
var vm = new Vue({
el: "vue-components",
components: [
"vue-person"
]
})
$(".js-change-value").on("click", function () {
vm.$refs.myPersonComponent.type = "human";
vm.$refs.myPersonComponent.age = 38;
})
<vue-components>
<vue-person name="Kevin" ref="myPersonComponent"></vue-person>
<button type="button" class="js-change-value">Change value</button>
</vue-components>
I would love to be able to omit the wrapper but as I'm already using other instances (new Vue()) on my page, I couldn't use for example '#main' (being the id on the body element) as it clashes with other global instances I already have on my page.

Ajax.ActionLink alternative with mvc core

In MVC5 there is #Ajax.ActionLink that is useful to update just a partial view instead of reloading the whole View. Apparently in MVC6 is not supported anymore.
I have tried using #Html.ActionLink like the following but it doesn't update the form, it return just the partial view:
View:
#Html.ActionLink("Update", "GetEnvironment", "Environments", new { id = Model.Id }, new
{
data_ajax = "true",
data_ajax_method = "GET",
data_ajax_mode = "replace",
data_ajax_update = "environment-container",
#class = "btn btn-danger"
})
control:
public async Task<ActionResult> GetEnvironment(int? id)
{
var environments = await _context.Environments.SingleOrDefaultAsync(m => m.Id == id);
return PartialView("_Environment",environments);
}
Partial view:
#model PowerPhysics.Models.Environments
this is a partial view
Then I tried using ViewComponents. When the page loads the component works correctly but I don't understand how to refresh just the component afterward (for example with a button):
View:
#Component.InvokeAsync("Environments", new { id = Model.Id }).Result
component:
public class EnvironmentsViewComponent : ViewComponent
{
public EnvironmentsViewComponent(PowerPhysics_DataContext context)
{
_context = context;
}
public async Task<IViewComponentResult> InvokeAsync(int? id)
{
var environments = await _context.Environments.SingleOrDefaultAsync(m => m.Id == id);
return View(environments);
}
}
How can I update just a part of a view by using PartialViews in MVC6?
You can use a tag as follows:
<a data-ajax="true"
data-ajax-loading="#loading"
data-ajax-mode="replace"
data-ajax-update="#editBid"
href='#Url.Action("_EditBid", "Bids", new { bidId = Model.BidId, bidType = Model.BidTypeName })'
class="TopIcons">Link
</a>
Make sure you have in your _Layout.cshtml page the following script tag at the end of the body tag:
<script src="~/lib/jquery/jquery.unobtrusive-ajax/jquery.unobtrusive-ajax.js"></script>
ViewComponent's are not replacement of ajaxified links. It works more like Html.Action calls to include child actions to your pages (Ex : Loading a menu bar). This will be executed when razor executes the page for the view.
As of this writing, there is no official support for ajax action link alternative in aspnet core.
But the good thing is that, we can do the ajaxified stuff with very little jQuery/javascript code. You can do this with the existing Anchor tag helper
<a asp-action="GetEnvironment" asp-route-id="#Model.Id" asp-controller="Environments"
data-target="environment-container" id="aUpdate">Update</a>
<div id="environment-container"></div>
In the javascript code, just listen to the link click and make the call and update the DOM.
$(function(){
$("#aUpdate").click(function(e){
e.preventDefault();
var _this=$(this);
$.get(_this.attr("href"),function(res){
$('#'+_this.data("target")).html(res);
});
});
});
Since you are passing the parameter in querystring, you can use the jQuery load method as well.
$(function(){
$("#aUpdate").click(function(e){
e.preventDefault();
$('#' + $(this).data("target")).load($(this).attr("href"));
});
});
I add ajax options for Anchor TagHelper in ASP.NET MVC Core
you can see complete sample in github link :
https://github.com/NevitFeridi/AJAX-TagHelper-For-ASP.NET-Core-MVC
after using this new tagHelper you can use ajax option in anchor very easy as shown below:
<a asp-action="create" asp-controller="sitemenu" asp-area="admin"
asp-ajax="true"
asp-ajax-method="get"
asp-ajax-mode="replace"
asp-ajax-loading="ajaxloading"
asp-ajax-update="modalContent"
asp-ajax-onBegin="showModal()"
asp-ajax-onComplete=""
class="btn btn-success btn-icon-split">
<span class="icon text-white-50"><i class="fas fa-plus"></i></span>
<span class="text"> Add Menu </span>
</a>
Use tag helpers instead and make sure to include _ViewImport in your views folder.
Note: Make sure to use document.getElementsByName if there are several links pointing to different pages that will update your DIV.
Example - Razor Page
<script type="text/javascript" language="javascript">
$(function () {
var myEl = document.getElementsByName('theName');
$(myEl).click(function (e) {
e.preventDefault();
var _this = $(this);
$.get(_this.attr("href"), function (res) {
$('#' + _this.data("target")).html(res);
});
});
});
</script>
<a asp-action="Index" asp-controller="Battle" data-target="divReplacable" name="theName" >Session</a>
<a asp-action="Index" asp-controller="Peace" data-target="divReplacable" name="theName" >Session</a>
<div id="divReplacable">
Some Default Content
</div>

asp.net mvc ajax.beginform being sent as html.beginform

I have a partial view from which I would like to display a modal dialog with updated data. User clicking the div would trigger both the display of the modal and the ajax call for the content of the modal to be updated.
<div class="nMmenuItem" >
#using (Ajax.BeginForm("editItem","nMrestaurant",new { id = Model.ID },
new AjaxOptions
{
HttpMethod = "get",
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "myModalDocument"
}, new { id = "ajaxEditItem" }))
{
<div data-toggle="modal" data-target="#myModal"
onclick="$('form#ajaxEditItem').submit();">
<div class="text-center">
#Model.name
</div>
</div>
}
</div>
I have a placeholder for the modal inside the parent view:
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
<div class="modal-dialog" role="document" id="myModalDocument">
#Html.Partial("_editItem", new nMvMmenuItem())
</div>
</div>
But while the controller action is expecting an AjaxResquest, the controller is evaluating Request.IsAjaxRequest() as false.
public async Task<ActionResult> editItem(int? id)
{
if (Request.IsAjaxRequest())
{
return PartialView("_editItem", await db.nMmenuItems.FindAsync(id));
}
return View();
}
Which refreshes the whole view and prevents the modal from working.
I am bundling the following scripts in the _Layout.cshtml page:
"~/Scripts/jquery-{version}.js",
"~/Scripts/jquery-ui-{version}.js",
"~/Scripts/jquery.unobstrusive*",
"~/Scripts/jquery.validate",
"~/Scripts/bootstrap.js",
"~/Scripts/respond.js"
Thanks for your help!
Check that you've got the unobtrusive ajax client scripts installed - your bundle pattern looks like it will pick them up if they are there, but I don't believe they are installed in the default project:
Install-Package Microsoft.jQuery.Unobtrusive.Ajax
While the Ajax.BeginForm is included in the standard MVC project, the client scripts are not and these are what is responsible for loading the content without refreshing the whole page.
I found that attaching submit() to the form's onclick event would not perform an ajax request.
My solution is thus to remove Ajax.SubmitForm and instead deal with the click event in my js:
The updated view looks like this:
<div class="nMmenuItem">
<form method="get" action="#Url.Action("editItem","nMrestaurant",new { id = Model.ID })"
data-nM-ajax="true" data-nM-target="#myModalContent">
<div>
<div class="text-center">
#Model.name
</div>
</div>
</form>
In the js I will bind the form submission to the click event of the parent div:
$('.nMmenuItem').click(ajaxFormSubmit);
And the function that handles the form submission and opens the resulting modal dialog:
var ajaxFormSubmit = function () {
var $form = $(this).children('form:first');
var options = {
url: $form.attr("action"),
type: $form.attr("method"),
data: $form.serialize()
};
$.ajax(options).done(function (data) {
var $target = $($form.attr("data-nM-target"));
$target.replaceWith(data);
$("#myModal").modal(dialogOpts);
});
return false;
};

JqueryMobile Loader/spinner while loading another page

Searched for almost 2 days and cant able to find a suitable answer.
I am developing a Jquery Mobile page. Currently I have 2 JQuery Mobile pages. When the page is launched it will show a button. Hitting the button will send a SOAP request to the server to get a response. After receiving the response the second page will be displayed.
The SOAP request might take a minimum of 3 to 5 seconds. During that time I would like to show a loader/spinner on the centre of the page till I get the response from the server. How to do that? Following is the code I use.
HTML File containing 2 pages
<form name="frm_login" action="" method="post">
<div id='pg_login' data-role="page">
<div data-role="content">
<input type="submit" name="btn_login_submit" id="btn_login_submit" value="Login" />
</div>
</div>
<div id='pg_menu' data-role="page">
<div data-role="header" data-position="fixed">
<h1>Welcome</h1>
</div>
</div>
</form>
Javascript code as below
$(document).ready(function() {
$('form').submit(function(e){
e.preventDefault();
var xmlRequest = getXmlRequest();
loadingStart();
$.soap({
url: 'full wsdl url',
method: 'getUserName',
data: xmlRequest,
success: function(xmlResponse) {
loadingEnd();
$.mobile.changePage('#pg_menu');
},
error: function(xmlResponse) {
}
});
return false;
});
});
function loadingStart(){
$.mobile.loading( 'show', {
text: "loading",
textVisible: true
});
}
function loadingEnd(){
$.mobile.loading( "hide" );
}
I also keep a 5 second sleep time in the WSDL function for testing purposes.
The loader is not displaying. Please let me know what is going wrong here.
Regards
Malai
The problem is because of the jQuery SOAP plugin (http://plugins.jquery.com/soap/)
After I change to native AJAX things started working fine with the below code.
$(document).ajaxStart(function() {
$.mobile.loading( 'show', {
text: "loading...",
textonly: false,
textVisible: true,
theme: 'a',
html: ""
});
});
$(document).ajaxStop(function() {
$.mobile.loading('hide');
});

backbone view passed to jQuery Mobile

I've been trying to use backbonejs and jqm together.
I can render the main page alright. The page has a list that the user can tap on. The item selected should show a detail page with info on the list item selected. The detail page is a backbone view with a template that's rendered in the item's view object.
The detail's view .render() produces the html ok and I set the html of the div tag of the main page to the rendered item's detail markup. It looks like this:
podClicked: function (event) {
console.log("PodListItemView: got click from:" + event.target.innerHTML + " id:" + (this.model.get("id") ? this.model.get("id") : "no id assigned") + "\n\t CID:" + this.model.cid);
var detailView = new PodDetailView({ model: this.model });
detailView.render();
},
The detail view's render looks like this:
render: function () {
this.$el.html(this.template({ podId: this.model.get("podId"), isAbout_Name: this.model.get("isAbout_Name"), happenedOn: this.model.get("happenedOn") }));
var appPageHtml = $(app.el).html($(this.el));
$.mobile.changePage(""); // <-- vague stab in the dark to try to get JQM to do something. I've also tried $.mobile.changePage(appPageHtml).
console.log("PodDetailView: render");
return this;
}
I can see that the detail's view has been rendered on the page by checking Chrome's dev tools html editor but it's not displaying on the page. All I see is a blank page.
I've tried $.mobile.changePage() but, without an URL it throws an error.
How do I get JQM to apply it's class tags to the rendered html?
the HTML and templates look like this:
<!-- Main Page -->
<div id="lessa-app" class="meditator-image" data-role="page"></div>
<!-- The rest are templates processed through underscore -->
<script id="app-main-template" type="text/template">
<div data-role="header">
<h1>#ViewBag.Title</h1>
</div>
<!-- /header -->
<div id="main-content" data-role="content">
<div id="pod-list" data-theme="a">
<ul data-role="listview" >
</ul>
</div>
</div>
<div id="main-footer" data-role='footer'>
<div id="newPod" class="ez-icon-plus"></div>
</div>
</script>
<script id="poditem-template" type="text/template">
<span class="pod-listitem"><%= isAbout_Name %></span> <span class='pod-listitem ui-li-aside'><%= happenedOn %></span> <span class='pod-listitem ui-li-count'>5</span>
</script>
<script id="page-pod-detail-template" type="text/template">
<div data-role="header">
<h1>Pod Details</h1>
</div>
<div data-role="content">
<div id='podDetailForm'>
<fieldset data-role="fieldcontain">
<legend>PodDto</legend>
<label for="happenedOn">This was on:</label>
<input type="date" name="name" id="happenedOn" value="<%= happenedOn %>" />
</fieldset>
</div>
<button id="backToList" data-inline="false">Back to list</button>
</div>
<div data-role='footer'></div>
</script>
Thanks in advance for any advice... is this even doable?
I've finally found a way to do this. My original code has several impediments to the success of this process.
The first thing to do is to intercept jquerymobile's (v.1.2.0) changePage event like this:
(I've adapted the outline from jqm's docs and left in the helpful comments: see http://jquerymobile.com/demos/1.2.0/docs/pages/page-dynamic.html
)
$(document).bind("pagebeforechange", function (e, data) {
// We only want to handle changePage() calls where the caller is
// asking us to load a page by URL.
if (typeof data.toPage === "string") {
// We are being asked to load a page by URL, but we only
// want to handle URLs that request the data for a specific
// category.
var u = $.mobile.path.parseUrl(data.toPage),
re = /^#/;
// don't intercept urls to the main page allow them to be managed by JQM
if (u.hash != "#lessa-app" && u.hash.search(re) !== -1) {
// We're being asked to display the items for a specific category.
// Call our internal method that builds the content for the category
// on the fly based on our in-memory category data structure.
showItemDetail(u, data.options); // <--- handle backbone view.render calls in this function
// Make sure to tell changePage() we've handled this call so it doesn't
// have to do anything.
e.preventDefault();
}
}
});
The changePage() call is made in the item's list backbone view events declaration which passes to the podClicked method as follows:
var PodListItemView = Backbone.View.extend({
tagName: 'li', // name of (orphan) root tag in this.el
attributes: { 'class': 'pod-listitem' },
// Caches the templates for the view
listTemplate: _.template($('#poditem-template').html()),
events: {
"click .pod-listitem": "podClicked"
},
initialize: function () {
this.model.bind('change', this.render, this);
this.model.bind('destroy', this.remove, this);
},
render: function () {
this.$el.html(this.listTemplate({ podId: this.model.get("podId"), isAbout_Name: this.model.get("isAbout_Name"), happenedOn: this.model.get("happenedOn") }));
return this;
},
podClicked: function (event) {
$.mobile.changePage("#pod-detail-page?CID='" + this.model.cid + "'");
},
clear: function () {
this.model.clear();
}
});
In the 'showItemDetail' function the query portion of the url is parsed for the CID of the item's backbone model. Again I've adapted the code provided in the jquerymobile.com's link shown above.
Qestion: I have still figuring out whether it's better to have the code in showItemDetail() be inside the view's render() method. Having a defined function seems to detract from backbone's architecture model. On the other hand, having the render() function know about calling JQM changePage seems to violate the principle of 'separation of concerns'. Can anyone provide some insight and guidance?
// the passed url looks like #pod-detail-page?CID='c2'
function showItemDetail(urlObj, options) {
// Get the object that represents the item selected from the url
var pageSelector = urlObj.hash.replace(/\?.*$/, "");
var podCid = urlObj.hash.replace(/^.*\?CID=/, "").replace(/'/g, "");
var $page = $(pageSelector),
// Get the header for the page.
$header = $page.children(":jqmData(role=header)"),
// Get the content area element for the page.
$content = $page.children(":jqmData(role=content)");
// The markup we are going to inject into the content area of the page.
// retrieve the selected pod from the podList by Cid
var selectedPod = podList.getByCid(podCid);
// Find the h1 element in our header and inject the name of the item into it
var headerText = selectedPod.get("isAbout_Name");
$header.html("h1").html(headerText);
// Inject the item info into the content element
var view = new PodDetailView({ model: selectedPod });
var viewElHtml = view.render().$el.html();
$content.html(viewElHtml);
$page.page();
// Enhance the listview we just injected.
var fieldContain = $content.find(":jqmData(role=listview)");
fieldContain.listview();
// We don't want the data-url of the page we just modified
// to be the url that shows up in the browser's location field,
// so set the dataUrl option to the URL for the category
// we just loaded.
options.dataUrl = urlObj.href;
// Now call changePage() and tell it to switch to
// the page we just modified.
$.mobile.changePage($page, options);
}
So the above provides the event plumbing.
The other problem I had was that the page was not set up correctly. It's better to put the page framework in the main html and not put it in an underscore template to be rendered at a later time. I presume that avoids issues where the html is not present when jqm takes over.
<!-- Main Page -->
<div id="lessa-app" data-role="page">
<div data-role="header">
<h1></h1>
</div>
<!-- /header -->
<div id="main-content" data-role="content">
<div id="pod-list" data-theme="a">
<ul data-role="listview">
</ul>
</div>
</div>
<div id="main-footer" data-role='footer'>
<div id="main-newPod" class="ez-icon-plus"></div>
</div>
</div>
<!-- detail page -->
<div id="pod-detail-page" data-role="page">
<div data-role="header">
<h1></h1>
</div>
<div id="detail-content" data-role="content">
<div id="pod-detail" data-theme="a">
</div>
</div>
<div id="detail-footer" data-role='footer'>
back
</div>
</div>

Resources