Angular template and .NET Partial Postback - c#

I have a trouble to get angular working with .Net partial postbacks.
Question is basically same to this : Re-initialize Angular bindings after partial postback
Basically I have a tab on which I have angular app, then I have second tab with some c# control, I have to do partial postback between tabs and when I am going back to my app, there is nothing.
I have tried routing with ngView then I have tried $route.reload() (it goes to the controller and I can see that the template is being pulled down but the result on the page is none). Then I tried compile(templateCache.get(lazyTableControllerRoute.current.templateUrl))(scope) as mentioned here. Nothing.
Please help :)
After each postback I am putting on page this html :
LiteralControl lazyControl = new LiteralControl("<div ng-app=\"LazyLoadingApp\" style=\"padding:10px\" ng-controller=\"LazyTableController\" ng-view><lazy-table> </lazy-table></div>");
Controls.Add(lazyControl);
And some config constants like templateUrl.
Here is my code :
var app = angular.module('LazyLoadingApp', ['ui.bootstrap', 'ngRoute'], function ($interpolateProvider) {
$interpolateProvider.startSymbol('[[');
$interpolateProvider.endSymbol(']]');
});
app.config(function ($routeProvider, $locationProvider, tableTemplateUrl) {
$routeProvider.when('/Page.Web.UI/sptl_project.aspx', {
controller: 'LazyTableController',
templateUrl: tableTemplateUrl,
});
// configure html5 to get links working on jsfiddle
$locationProvider.html5Mode(true);
});
//**This objects I am using after partial postback to check in the console if e.g. $route.reload() works..**
var lazyTableControllerRoute = null;
var templateCache = null;
var compile = null;
var scope = null;
app.directive('lazyTable', ['tableTemplateUrl',
function (tableTemplateUrl) {
return {
name: 'lazyTable',
priority: 0,
restrict: 'E', // E = Element, A = Attribute, C = Class, M = Comment
templateUrl: tableTemplateUrl
};
}
]).controller('LazyTableController', ['$scope', '$rootScope', 'lazyFactory', 'opsPerRequest', 'header', '$route', '$templateCache', '$compile',
function ($scope, $rootScope, lazyFactory, opsPerRequest, header, $route, $templateCache, $compile) {
lazyTableControllerRoute = $route;
var loadingPromise = null;
templateCache = $templateCache;
compile = $compile;
scope = $scope;
(...) rest is not important
UPDATE:
I was trying with require.js.. (Again, it's working after full page load.) My idea was to bootstrap element after partial postback.
I built simple test case that in Update Panel along with my app there is simple button, just making partial postback. After click (when app disappeared) I tried in console:
angular.bootstrap(document, ['LazyLoadingApp'])
But then I got error which I cannot remove:
App Already Bootstrapped with this Element 'document'
Here is plunker for app in require.js way (but please keep in mind that it's just for code review purpose..)

don't use ng-app and angular.bootstrap all together at once.
did you compile "ng-app", if so don't.
ngtutorial.com/learn/bootstrap

Alright ! Problem solved.. So in order to work with partial postbacks you need to :
Define app like this: (remember that you have to remove ng-app from html !)
<base href="/">
<asp:UpdatePanel ID="updatePanel" runat="server" UpdateMode="Conditional">
<ContentTemplate>
<asp:Button ID="fire" runat="server" Text="fire!" />
<div id="parent" > <div id="boodstapped" ng-view ></div></div>
</ContentTemplate>
</asp:UpdatePanel>
Instantiate app like this:
var app = angular.module('LazyLoadingApp', ['ui.bootstrap', 'ngRoute']);
jQuery(document).ready(function ($) {
var prm = Sys.WebForms.PageRequestManager.getInstance();
function EndRequestHandler(sender, args) {
jQuery('#parent').children().remove();
jQuery('#parent').append('<div id="boodstapped" ng-view ></div>');
angular.bootstrap(jQuery('#boodstapped'), ['LazyLoadingApp']);
}
prm.add_endRequest(EndRequestHandler);
});
Then when you will try to do partial postback EndRequestHandler will bootstrap app again. What's important to remove previously bootstrap element, to avoid angular "already bootstrapped" error.
Click Here for more information.

Though the other answers probably do work I've found that best course of action is to try to not be in a 'mixed' state of .net controls and angular controls that depend on interaction between the two.
The process for this is undocumented at best... and you end up writing a lot of code to accommodate simple things.
I wouldn't recommend doing the following for long and only suggest it as a temporary/transitional solution and I know this may not solve every case .... but note you can always attack this problem from the dot.net side as follows and redirect back to the page you are on...
protected void cbChecked_My_DotNet_CallBack(object sender, EventArgs e)
{
DoDotNetStuff();
if(NeedToSignalToAngular){
response.redirect(yourpage.aspx?yourparams=xxx)
}
}

Related

How do I store the value of a #Html.TextBox to a variable

I'm a newbie to Razor and Asp.Net. I'm mostly a Winforms developer. I must say I don't like what I've been seeing in ASP, over complicated to say the least. Anyways, I want to take the value from a #Html.TextBox and store it to a variable. I've searched on here an can't find a solution. There has to be a simple way of doing this.
I've been trying to pull the value out with Request.Froms but keeps crashing
#Html.TextBox("test")
#Html.TextBox("test2")
#{
var z = Request.Form["test"];
var x = Request.Form["test2"];
}
All I want is to store the inputted value to z and x. Meaning if I input 2 into test and 3 in to test2 I want z = 2 and x = 3.
Depending on your use case, there are several ways to get the values:
If you need the values in the frontend (e.g. without reload of the page), you can use JavaScript/jQuery to get the values of the textbox. That is because when the user interacts with the inputs, the Razor code already has been executed and turned e.g. into HTML (also see this question). You could set your variables when the inputs are changed using jQuery.change() as follows:
<script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-1.9.0.js"></script>
<script type="text/javascript">
var x = null;
var y = null;
$(document).ready(function () {
$("#test").change(function () {
x = $("#test").val();
alert(x);
});
$("#test2").change(function () {
y = $("#test2").val();
alert(y);
});
});
</script>
If it is sufficient to have the values after a reload of the page, you can put the inputs in a form, and submit that to the controller, where the values are available in the request or using model binding (see this tutorial for an example of how to do that in ASP.NET Core MVC). A basic example for your scenario:
In the view:
<form asp-controller="Home" asp-action="Test" method="post">
#Html.TextBox("test")
#Html.TextBox("test2")
<button type="submit">Submit</button>
</form>
And in the Home controller (note that here you can access the inputs using the Request):
[HttpPost]
public ActionResult Test()
{
var x = Request.Form["test"].First();
var y = Request.Form["test2"].First();
return RedirectToAction("Index");
}

How to create HTML page by code [duplicate]

How would I open a new window in JavaScript and insert HTML data instead of just linking to an HTML file?
I would not recomend you to use document.write as others suggest, because if you will open such window twice your HTML will be duplicated 2 times (or more).
Use innerHTML instead
var win = window.open("", "Title", "toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=yes,resizable=yes,width=780,height=200,top="+(screen.height-400)+",left="+(screen.width-840));
win.document.body.innerHTML = "HTML";
You can use window.open to open a new window/tab(according to browser setting) in javascript.
By using document.write you can write HTML content to the opened window.
When you create a new window using open, it returns a reference to the new window, you can use that reference to write to the newly opened window via its document object.
Here is an example:
var newWin = open('url','windowName','height=300,width=300');
newWin.document.write('html to write...');
Here's how to do it with an HTML Blob, so that you have control over the entire HTML document:
https://codepen.io/trusktr/pen/mdeQbKG?editors=0010
This is the code, but StackOverflow blocks the window from being opened (see the codepen example instead):
const winHtml = `<!DOCTYPE html>
<html>
<head>
<title>Window with Blob</title>
</head>
<body>
<h1>Hello from the new window!</h1>
</body>
</html>`;
const winUrl = URL.createObjectURL(
new Blob([winHtml], { type: "text/html" })
);
const win = window.open(
winUrl,
"win",
`width=800,height=400,screenX=200,screenY=200`
);
You can open a new popup window by following code:
var myWindow = window.open("", "newWindow", "width=500,height=700");
//window.open('url','name','specs');
Afterwards, you can add HTML using both myWindow.document.write(); or myWindow.document.body.innerHTML = "HTML";
What I will recommend is that first you create a new html file with any name.
In this example I am using
newFile.html
And make sure to add all content in that file such as bootstrap cdn or jquery, means all the links and scripts. Then make a div with some id or use your body and give that a id. in this example I have given id="mainBody" to my newFile.html <body> tag
<body id="mainBody">
Then open this file using
<script>
var myWindow = window.open("newFile.html", "newWindow", "width=500,height=700");
</script>
And add whatever you want to add in your body tag. using following code
<script>
var myWindow = window.open("newFile.html","newWindow","width=500,height=700");
myWindow.onload = function(){
let content = "<button class='btn btn-primary' onclick='window.print();'>Confirm</button>";
myWindow.document.getElementById('mainBody').innerHTML = content;
}
myWindow.window.close();
</script>
it is as simple as that.
You can also create an "example.html" page which has your desired html and give that page's url as parameter to window.open
var url = '/example.html';
var myWindow = window.open(url, "", "width=800,height=600");
Use this one. It worked for me very perfect.
For New window:
new_window = window.open(URL.createObjectURL(new Blob([HTML_CONTENT], { type: "text/html" })))
for pop-up
new_window = window.open(URL.createObjectURL(new Blob([HTML_CONTENT], { type: "text/html" })),"width=800,height=600")
Replace HTML_CONTENT with your own HTML Code
Like:
new_window = window.open(URL.createObjectURL(new Blob(["<h1>Hello</h1>"], { type: "text/html" })))
if your window.open() & innerHTML works fine, ignore this answer.
following answer only focus on cross-origin access exception
#key-in_short,workaround:: [for cross-origin access exception]
when you exec code in main.html -- which tries to access file window_ImageGallery.html by using window.open() & innerHTML
for anyone who encounter cross-origin access exception
and you dont want to disable/mess_around_with Chrome security policy
-> you may use query string to transfer the html code data, as a workaround.
#details::
#problem-given_situation,#problem-arise_problem::
say you exec following simple window.open command as other answer suggested.
let window_Test = window.open('window_ImageGallery.html', 'Image Enlarged Window' + $(this).attr('src'), 'width=1000,height=800,top=50,left=50');
window_Test.document.body.innerHTML = 'aaaaaa';
you may encounter following cross-origin access exception
window_Test.document.body.innerHTML = 'aaaaaa'; // < Exception here
Uncaught DOMException: Blocked a frame with origin "null" from accessing a cross-origin frame.
=> #problem-solution-workaround::
you may use query string to transfer the html code data, as a workaround. <- Transfer data from one HTML file to another
#eg::
in your main.html
// #>> open ViewerJs in a new html window
eleJq_Img.click(function() {
// #>>> send some query string data -- a list of <img> tags, to the new html window
// #repeat: must use Query String to pass html code data, else you get `Uncaught DOMException: Blocked a frame with origin "null" from accessing a cross-origin frame.` (cross origin access issue)
let id_ThisImg = this.id;
let ind_ThisImg = this.getAttribute('data-index-img');
let url_file_html_window_ImageGallery = 'window_ImageGallery.html'
+ '?queryStr_html_ListOfImages=' + encodeURIComponent(html_ListOfImages)
+ '&queryStr_id_ThisImg=' + encodeURIComponent(id_ThisImg)
+ '&queryStr_ind_ThisImg=' + encodeURIComponent(ind_ThisImg);
// #>>> open ViewerJs in a new html window
let window_ImageGallery = window.open(url_file_html_window_ImageGallery, undefined, 'width=1000,height=800,top=50,left=50');
});
in your window_ImageGallery.html
window.onload = function () {
// #>> get parameter from URL
// #repeat: must use Query String to pass html code data, else you get `Uncaught DOMException: Blocked a frame with origin "null" from accessing a cross-origin frame.` (cross origin access issue)
// https://stackoverflow.com/questions/17502071/transfer-data-from-one-html-file-to-another
let data = getParamFromUrl();
let html_ListOfImages = decodeURIComponent(data.queryStr_html_ListOfImages);
let id_ThisImgThatOpenedTheHtmlWindow = decodeURIComponent(data.queryStr_id_ThisImg);
let ind_ThisImgThatOpenedTheHtmlWindow = decodeURIComponent(data.queryStr_ind_ThisImg);
// #>> add the Images to the list
document.getElementById('windowImageGallery_ContainerOfInsertedImages').innerHTML = html_ListOfImages;
// -------- do your stuff with the html code data
};
function getParamFromUrl() {
let url = document.location.href;
let params = url.split('?')[1].split('&');
let data = {};
let tmp;
for (let i = 0, l = params.length; i < l; i++) {
tmp = params[i].split('=');
data[tmp[0]] = tmp[1];
}
return data
}
#minor-note::
(seems) sometimes you may not get the cross-origin access exception
due to, if you modify the html of 'window_ImageGallery.html' in main.html before window_ImageGallery.html is loaded
above statement is based on my test
& another answer -- window.open: is it possible open a new window with modify its DOM
if you want to make sure to see that Exception,
you can try to wait until the opening html window finish loading, then continue execute your code
#eg::
use defer() <- Waiting for child window loading to complete
let window_ImageGallery = window.open('window_ImageGallery.html', undefined, 'width=1000,height=800,top=50,left=50');
window_ImageGallery.addEventListener("unload", function () {
defer(function (){
console.log(window_ImageGallery.document.body); // < Exception here
});
});
function defer (callback) {
var channel = new MessageChannel();
channel.port1.onmessage = function (e) {
callback();
};
channel.port2.postMessage(null);
}
or use sleep() with async What is the JavaScript version of sleep()?
eleJq_Img.click(async function() {
...
let window_Test = window.open( ...
...
await new Promise(r => setTimeout(r, 2000));
console.log(window_Test.document.body.innerHTML); // < Exception here
});
or you get null pointer exception
if you try to access elements in window_ImageGallery.html
#minor-comment::
There are too many similar Posts about the cross-origin issue. And there are some posts about window.open()
Idk which post is the best place to place the answer. And I picked here.

MVC 5 Losing validator

I have noticed a strange behavior in MVC 5 (C#) with the form validator.
This is my code to check on keyup for errors:
var $validatr = $('form').data('validator');
var settngs = $validatr.settings;
settngs.onkeyup = function (element, eventType) {
if (!$validatr.element(element)) {
$(this.currentForm).triggerHandler("invalid-form", [this]);
}
};
settngs.onfocusout = false;
I have noticed that this code works on some forms, on other not. I tried to get the validator also like this:
var $validatr = $('form').validate();
But it is still not working. Important: then I noticed that the the code is working for the registration form only if the user is not logged already. (I can access the registration form also when the user is logged). When the user is logged I get this error:
TypeError: $validatr is undefined
In this case the error for the input form pops-up only when there is an out_of_focus of the element.
UPDATE:
If I delete this cookie: AspNet.ApplicationCookie and refresh the page the user is logged out and the onKeyUp validation is working. What is going on?
If you have more than one form on a page or you have a popup that contains a form on the page the Jquery selector can have multiple forms targeted the way you are using your selector. I would suggest you target a form by Id instead
var $validatr = $('#myForm1').data('validator');
Or alternatively using a loop to target all form elements
$('form').each(function( index, form ) {
var settngs = form.settings;
settngs.onkeyup = function (element, eventType) {
if (!form.element(element)) {
form.triggerHandler("invalid-form", [this]);
}
};
settngs.onfocusout = false;
});

Using C# Script in SharePoint Page

I've done quite a bit of searching (several hours actually) but I haven't been able to get this working. Basically, I have this button:
<asp:Button runat="server" Text="Go!" id="go" onClick="getDoc()" />
and this block of script:
<script type="c#" runat="server">
public void getDoc(object sender, EventArgs e) {
// Test to see if function was running (it's not...)
DocFrame.Attributes["src"] = "http://www.google.com";
// Get the current state of the dropdowns
String dropYear = (String)Year.SelectedValue;
String dropDiv = (String)Division.SelectedValue;
String dropControl = (String)Control.SelectedValue;
String dropQuart= (String)Quarter.SelectedValue;
// Get the Site where the list is
using (SPSite siteCol = new SPSite("http://portal/Corporate/IT/")) {
using (SPWeb web = siteCol.RootWeb){
// Get the list items we need
SPListItemCollection items = list.GetItems("Year", "Division", "Control", "Quarter");
SPListItem item = null;
// Loop through them until we find a matching everything
foreach (SPListItem it in items){
if(it.Year == dropYear && it.Division == dropDiv && it.Control == dropControl && it.Quarter == dropQuart){
item = it;
break;
}
}
// Assign the item as a string
String URL = (String)item["Title"];
// Set the iframe to the new URL
DocFrame.Attributes["src"] = URL;
}
}
}
It's all in the page where this is happening, please keep in mind that I've been using sharepoint for less than a week and have only ever coded in C++, so I could be doing everything horribly wrong. Anyway, it seems that getDoc() is never even getting called, so can anyone point out what I'm doing wrong?
Instead of
onClick="getDoc()"
you should do
OnClick="getDoc"
That's the proper way to wire an up an event.
By the way, you should consider following C# Naming Guidelines. If you were using better naming, it might look like this:
<asp:Button runat="server" Text="Go!" id="GoBtn" onClick="GoBtn_Click" />
Common practice convention is to append the event name after the ID of the control. It's not required, but it looks cleaner and other developers like to see that when they look at your code.
Also, DocFrame.Attributes["src"] = "http://www.google.com"; is not a good way to see if the function is running. It doesn't update the page in realtime, as the entire server side function executes, then the results are sent to the client. Instead, use your IDE's debugging tools to hook up to the server and set code breaks etc. Or what I do is have the code send me an email, I created a little utility library for that.

Invoke JavaScript from C# code behind [duplicate]

This question already has answers here:
Calling JavaScript Function From CodeBehind
(21 answers)
Closed 9 years ago.
I am trying to learn asp.net. Assuming that I have this code:
if (command.ExecuteNonQuery() == 0)
{
// JavaScript like alert("true");
}
else
{
// JavaScript like alert("false");
}
How to I can invoke JavaScript from C# code behind? How to do that by putting that JavaScript in Scripts directory which is created by default in MS Visual Studio?
Here is method I will use from time to time to send a pop message from the code behind. I try to avoid having to do this - but sometimes I need to.
private void LoadClientScriptMessage(string message)
{
StringBuilder script = new StringBuilder();
script.Append(#"<script language='javascript'>");
script.Append(#"alert('" + message + "');");
script.Append(#"</script>");
Page.ClientScript.RegisterStartupScript(this.GetType(), "messageScript", script.ToString());
}
You can use RegisterStartupScript to load a javascript function from CodeBehind.
Please note that javascript will only run at client side when the page is render at client's browser.
Regular Page
Page.ClientScript.RegisterStartupScript(this.GetType(), "myfunc" + UniqueID,
"myJavascriptFunction();", true);
Ajax Page
You need to use ScriptManager if you use ajax.
ScriptManager.RegisterStartupScript(Page, Page.GetType(), "myfunc" + UniqueID,
"myJavascriptFunction();", true);
Usually these "startupscripts" are handy for translations or passing settings to javascript.
Although the solution Mike provided is correct on the .Net side I doubt in a clean (read: no spaghetti code) production environment this is a good practice. It would be better to add .Net variables to a javascript object like so:
// GA example
public static string GetAnalyticsSettingsScript()
{
var settings = new StringBuilder();
var logged = ProjectContext.CurrentUser != null ? "Logged" : "Not Logged";
var account = Configuration.Configuration.GoogleAnalyticsAccount;
// check the required objects since it might not yet exist
settings.AppendLine("Project = window.Project || {};");
settings.AppendLine("Project.analytics = Project.analytics || {};");
settings.AppendLine("Project.analytics.settings = Project.analytics.settings || {};");
settings.AppendFormat("Project.analytics.settings.account = '{0}';", account);
settings.AppendLine();
settings.AppendFormat("Project.analytics.settings.logged = '{0}';", logged);
settings.AppendLine();
return settings.ToString();
}
And then use the common Page.ClientScript.RegisterStartupScript to add it to the HTML.
private void RegisterAnalyticsSettingsScript()
{
string script = GoogleAnalyticsConfiguration.GetAnalyticsSettingsScript();
if (!string.IsNullOrEmpty(script))
{
Page.ClientScript.RegisterStartupScript(GetType(), "AnalyticsSettings", script, true);
}
}
On the JavaScript side it might look like this:
// IIFE
(function($){
// 1. CONFIGURATION
var cfg = {
trackingSetup: {
account: "UA-xxx-1",
allowLinker: true,
domainName: "auto",
siteSpeedSampleRate: 100,
pluginUrl: "//www.google-analytics.com/plugins/ga/inpage_linkid.js"
},
customVariablesSetup: {
usertype: {
slot: 1,
property: "User_type",
value: "Not Logged",
scope: 1
}
}
};
// 2. DOM PROJECT OBJECT
window.Project = window.Project || {};
window.Project.analytics = {
init: function(){
// loading ga.js here with ajax
},
activate: function(){
var proj = this,
account = proj.settings.account || cfg.trackingSetup.account,
logged = proj.settings.logged || cfg.customVariablesSetup.usertype.value;
// override the cfg with settings from .net
cfg.trackingSetup.account = account;
cfg.customVariablesSetup.usertype.value = logged;
// binding events, and more ...
}
};
// 3. INITIALIZE ON LOAD
Project.analytics.init();
// 4. ACTIVATE ONCE THE DOM IS READY
$(function () {
Project.analytics.activate();
});
}(jQuery));
The advantage with this setup is you can load an asynchronous object and override the settings of this object by .Net. Using a configuration object you directly inject javascript into the object and override it when found.
This approach allows me to easily get translation strings, settings, and so on ...
It requires a little bit knowledge of both.
Please note the real power of tis approach lies in the "direct initialization" and "delayed activation". This is necessary as you might not know when (during loading of the page) these object are live. The delay helps overriding the proper objects.
This might be a long shot, but sometimes I need a c# property/value from the server side displaying or manipulated on the client side.
c# code behind page
public string Name {get; set;}
JavaScript on Aspx page
var name = '<%=Name%>';
Populating to client side is generally easier, depending on your issue. Just a thought!

Categories