I have a Cefsharp chromium browser and a simple web application hosted with Nancy on a local port built into a wpf application. I would like to use angular with my web application, but I am struggling to change variables inside the angular scope.
Directly on the angular page, everything works perfectly. However, when I try to cross the gap between C# and JS, it partially fails. When I fire the call off from C#, the alert windows still appear and the value of report_type does appear to change in the alert box. However, in the ng-switch, nothing is updated. It's almost as if I am not accessing the correct scope when firing the call from C#... yet if that were the case, the methods in the angular scope shouldn't be callable from C#.
in C#, I call this:
private void GIS_Button_Click(object sender, RoutedEventArgs e)
{
this.report_browser.GetBrowser().MainFrame.ExecuteJavaScriptAsync("ext_switch_gis();");
}
On the served web page:
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
$scope.switch_gis = function switch_gis() {
alert("begin switch gis");
$scope.report_type = "gis";
alert("end switch gis");
alert("report value is: " + $scope.report_type);
}
$scope.switch_bar = function switch_bar() {
alert("begin switch bar");
$scope.report_type = "bar";
alert("end switch bar");
alert("report value is: " + $scope.report_type);
}
$scope.mytest = function mytest(words) {
alert(words);
}
$scope.switch_bar();
});
function ext_switch_gis() {
var outside_scope = angular.element(document.getElementById('myAppDiv')).scope();
outside_scope.mytest("Beginning of external js call!");
outside_scope.switch_gis();
outside_scope.mytest("End of external js call!");
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.9/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.0/jquery.min.js"></script>
<div id="myAppDiv" ng-app="myApp" ng-controller="myCtrl">
<div id="report_switch_div" ng-switch="report_type">
<div ng-switch-when="bar">
<h1>bar</h1>
</div>
<div ng-switch-when="gis">
<h1>gis</h1>
</div>
<div ng-switch-default>
<h1>Select a report type</h1>
</div>
</div>
<button ng-click="switch_gis()">gis test</button>
<button ng-click="switch_bar()">bar test</button>
</div>
Found a solution. It seems that when invoking a JS function externally, it may often be necessary to use $apply to make sure the "magic" behind angular that allows for 2 way bindings continues to take effect.
This article was very helpful to me: http://jimhoskins.com/2012/12/17/angularjs-and-apply.html
The actual code change I made was this:
$scope.switch_gis = function switch_gis() {
$scope.$apply(function () {
$scope.report_type = "gis";
});
}
Related
I have a rather large web application that I'm upgrading from React 16 to React 18 and overhauling completely, it uses .NET 6. Currently, it's not using any of the hydration SSR techniques. I've looked into this for quite some time now, but am not finding any solid examples to help me through this technique. I thought this might be a good thread to make for future developers who may be doing the same. Basically, I have pages with a collection of 'widgets' (React Components), some of the components require more data to load than others, some don't need any data. The issue is that all the data is loaded from the C# controller via API calls and put into a big model/object, which contains the data for each individual widget on the page, so no widgets are loaded until all the data is gathered and passed in from the controller. Give examples of how to make this work best, using available React techniques, I'm sure there is probably a couple of great designs for this. I'd like to get things set-up so that I can at least do one of the two techniques, the first being the preferred method:
Load data from the C# Controller to each individual React Component directly as the data is gathered, having the individual widget render at that time. Then, when the data is ready for another widget, it's passed into the particular component and rendered on screen. This will make it so that no component depends on the data needing to be loaded for other components for it to load.
Something I want to avoid: Some of the 'pages' currently use Fetch (GET/POST) calls from the JSX files to load data from the API, rather than from the controller when the page link is clicked. This has caused issues in the past from having too many fetch calls going at the same time from different widgets loading (collisions/etc), which was just causing them to crash a lot, so I've been modifying all of these widget's data to be loaded from the controller beforehand.
Having a 'loading' screen of sorts show up or even just the widgets' container html, appear until all of the data is passed from the controller via the data model.
How I currently have everything set-up:
-> From home page, user clicks on link to go to their Account profile at 'Account\Index\userId'
-> Example of current AccountController.cs Index call:
[HttpGet]
public async Task<ActionResult> Index(int? userId) {
var model = new AccountViewModelContract();
model.NameData = await ApiClient1.GetNameViewModelAsync(userId);
model.AccountSecurityData = await ApiClient2.GetAccountSecurityViewModelAsync(userId);
model.EmailData = await ApiClient2.GetEmailViewModelAsync(userId);
model.PhoneData = await ApiClient2.GetPhoneViewModelAsync(userId);
model.AddressData = await ApiClient3.GetAddressViewModelAsync(userId);
model.LoadedFromController = true;
return View(model);
}
-> Example of current Account's View, Index.cshtml
#using App.Models
#model AccountViewModelContract
<input id="accountPageData" type='hidden' value="#System.Text.Json.JsonSerializer.Serialize(Model)" />
<div id="accountDash" style="height:100%;"> </div>
#section ReactBundle{
<script src="#Url.Content("~/Dist/AccountDashboard.bundle.js")" type="text/javascript"></script>
}
-> Example of current AccountDashboard.jsx:
import React from 'react';
import { createRoot } from 'react-dom/client';
import BootstrapFooter from 'Shared/BootstrapFooter';
import AccountSecurity from './AccountSecurity/AccountSecurity';
import Address from 'Account/Address/Address';
import EmailSettings from 'Account/Email/EmailSettings';
import Name from 'Account/Name/Name';
import Phone from 'Account/Phone/Phone';
import PropTypes from 'prop-types';
import BootstrapHeaderMenu from '../Shared/BootstrapHeaderMenu';
class AccountDashboard extends React.Component {
constructor(props) {
super(props);
const data = JSON.parse(props.accountPageData);
this.state = { data };
}
render() {
const { data } = this.state;
return (
<div className="container">
<div className="row">
<BootstrapHeaderMenu/>
</div>
<div>My Account</div>
<div className="row">
<div className="col">
<Name value={data.NameData} />
</div>
<div className="col">
<Phone value={data.PhoneData} />
</div>
<div className="col">
<Address value={data.AddressData} />
</div>
<div className="col">
<AccountSecurity value={data.AccountSecurityData} />
</div>
<div className="col">
<EmailSettings value={data.EmailData} />
</div>
</div>
<div className="row">
<BootstrapFooter/>
</div>
</div>
);
}
}
AccountDashboard.propTypes = {
accountPageData: PropTypes.string.isRequired
};
createRoot(document.getElementById('accountDash')).render(<AccountDashboard accountPageData={document.getElementById('accountPageData').value} />);
-> Example of one of the Widget's (Name.jsx), code condensed to not complicate things
import React from 'react';
import BlueCard from 'Controls/BlueCard';
import LoadingEllipsis from 'Controls/LoadingEllipsis';
class Name extends React.Component {
constructor(props) {
super(props);
const data = props.value;
this.state = {
isLoaded: false,
.....
loadedFromController: props.value.LoadedFromController,
};
if (props.value.LoadedFromController) {
...set more state data
}
}
componentDidMount() {
if (!this.state.isLoaded && !this.state.loadedFromController) {
..//go to server to get data, widget sometimes loads this way in odd certain cases, pay no attention
} else {
this.setState({
isLoaded: true
});
}
render() {
let content = <LoadingEllipsis>Loading Name</LoadingEllipsis>;
const { ... } = this.state;
if (isLoaded === true) {
content = (<div>....fill content from state data</div>);
}
return (<BlueCard icon="../../Content/Images/Icon1.png" title="Name" banner={banner} content={content} />);
}
}
export default Name;
What are some methods, techniques, or design patterns for accomplishing these goals and give detailed examples of how it can be done and why it works better than alternatives. Use the above example class names in your examples.
Regarding the mentioned approaches:
Load data from the C# Controller to each individual React Component
directly as the data is gathered, having the individual widget render at that time. Then, when the data is ready for another widget, it's passed into the particular component and rendered on screen. This will make it so that no component depends on the data needing to be loaded for other components for it to load.
In order to create something like this, the use of Fetch calls in React are needed, to render the data from the Controller. You would just fetch the data for each widget/component within the react component. Unfortunately, it goes against MVC and React patterns to inject html directly to the view from the controller. In addition, there's no way to hand over control of the browser to the controller dynamically in a way where you could inject/return multiple individual views at different times, depending on when data is loaded from API calls.
Having a 'loading' screen of sorts show up or even just the widgets'
container html, appear until all of the data is passed from the
controller via the data model.
The issue mentioned with the above approach is that when a user clicks to go to a next page, the controller is called in order to decide what View to return. You cannot return a quick view and then return another view when the data is loaded for it. When you return control to the view, the controller is then useless.
The approach that I took instead:
I created a Main View that's used everywhere, switched to routing (react-router-dom), lazy loading, and now mostly utilize only one main controller.
Example:
Main.cs
[Authorize]
[HttpGet]
public async Task<ActionResult> Index(int? id) {
if(User.Identity != null && User.Identity.IsAuthenticated) {
var model = await GetMainViewModel(id);
return View(model);
}
return View();
}
[HttpGet]
[Route("AccountDashboard")]
public async Task<ActionResult> AccountDashboard(int? id) {
if(User.Identity != null && User.Identity.IsAuthenticated) {
var model = await GetMainViewModel(id);
return View(model);
}
return View();
}
[HttpGet]
[Route("AnotherDashboard")]
public async Task<ActionResult> AnotherDashboard(int? id) {
if(User.Identity != null && User.Identity.IsAuthenticated) {
var model = await GetMainViewModel(id);
return View(model);
}
return View();
}
Example Index.html that's used by every dashboard:
#using Main.Models
#model MainViewModel
<input id="PageData" type='hidden' value="#System.Text.Json.JsonSerializer.Serialize(Model)" />
<div id="root"></div>
#section ReactBundle{
<script src="#Url.Content("~/Dist/Main.bundle.js")"></script>
}
Example Main.jsx:
import { createRoot } from 'react-dom/client';
import { Suspense } from "react";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import BootstrapHeader from "BootstrapHeader";
import BootstrapFooter from "BootstrapFooter";
import LandingPage from "./LandingPage";
const AccountDashboard = React.lazy(() => import('../AccountDashboard'));
const AnotherDashboard = React.lazy(() => import('../AnotherDashboard'));
class Main extends React.Component {
constructor(props) {
super(props);
var data = JSON.parse(props.PageData);
this.state = {
data: data,
};
}
render() {
return(
<BrowserRouter>
<div className={} style={{ minHeight: '100vh' }}>
<div style={{ minHeight: '11vh' }}>
<BootstrapHeader value={this.state.data} />
</div>
<div className="container-column" style={{ minHeight: '75vh' }}>
<Routes>
<Route exact path="/" element={<Suspense fallback={<div>Loading...</div>}><LandingPage PageData={this.state.data}/></Suspense>} />
<Route exact path="/AccountDashboard" element={<Suspense fallback={<div>Loading...</div>}><AccountDashboard AccountData={this.state.data.AccountModels} /></Suspense>} />
<Route exact path="/AnotherDashboard" element={<Suspense fallback={<div>Loading...</div>}><AnotherDashboard AnotherData={this.state.data.AnotherModels} /></Suspense>} />
</Routes>
</div>
<div style={{ minHeight: '14vh' }}>
<BootstrapFooter value={this.state.data} />
</div>
</div>
</BrowserRouter>
);
}
}
createRoot(document.getElementById('root')).render(<Main PageData={document.getElementById('PageData').value} />);
Each Dashboard has it's own widgets, which rely on data from the main model. Basically, all of the data is grabbed up front for each user, for the whole website. I found that the amount of data was worth just grabbing ahead of time rather than right before each widget loads. With the use of caching techniques, the App is running super fast now. Putting in the Route names as methods in the Main.cs allowed is so that the page can be reloaded and not fail, which is common with routing.
The following page is based on the sample Angular page which is generated by Visual Studio for a new Angular web application:
<h1>Member Module</h1>
<p>This is a simple example of an Angular component.</p>
<p aria-live="polite">Current count: <strong>{{ currentCount }}</strong></p>
<button class="btn btn-primary" (click)="incrementCounter()">Increment</button>
<div ng-app="myApp" ng-controller="myController">
<table>
<tr ng-repeat="x in portfolios">
<td>{{ x }}</td>
</tr>
</table>
</div>
<script>
var app = angular.module('myApp', []);
app.controller('myController', function ($scope, $http) {
$http.get("https://localhost:44319/api/Portfolio")
.then(function (response) { $scope.portfolios = response.text(); });
});
</script>
The button counter works so that pretty much confirms that Angular support is present.
I've added some additional angular code into that page. Beginning with the div tag is some sample code which I'm basing on the tutorial which I find here: https://www.w3schools.com/angular/angular_sql.asp. This will be my first foray into fetching data from the backend SQL Server and displaying it on a web page using Angular.
I've set a breakpoint inside of my controller: https://localhost:44319/api/Portfolio
If I hit that URL manually in a browser window, the breakpoint is hit as expected. But when I load the page, I get no breakpoint. So the script is not being run, but I cannot understand why not.
Can you help me with this? Thank you!
move your api call to some function and call that function like
var init=function(){
$http.get("https://localhost:44319/api/Portfolio")
.then(function (response) { $scope.portfolios = response.text(); });
}
init();
Also there is nothing like (click) in angularjs it should be ng-click
Related SO
Thank you all for clarifying the point. My example fails because I have been conflating Angular with AngularJS. My project, produced by Visual Studio 2019 is Angular NOT AngularJS. But the sample I pulled off the web is AngularJS - of course it's not going to work.
For anyone who might be in the same predicament, here is some code which you can use to augment the auto-magically generated sample pages with some of your own code to get a feel for how Angular is constructed.
member.component.html:
<h1>Member Module</h1>
<p>This is a simple example of an Angular component.</p>
<p aria-live="polite">Current count: <strong>{{ currentCount }}</strong></p>
<button class="btn btn-primary" (click)="incrementCounter()">Increment</button>
<hr />
<h1>{{title}}</h1>
<h2>My favorite hero is: {{heroes[currentCount]}}</h2>
<p>Heroes:</p>
<ul>
<li *ngFor="let hero of heroes">
{{ hero }}
</li>
</ul>
member.component.ts:
import { Component } from '#angular/core';
#Component({
selector: 'app-member-component',
templateUrl: './member.component.html'
})
export class MemberComponent {
public currentCount = 0;
public title = 'Tour of Heroes';
public heroes = ['Windstorm', 'Bombasto', 'Magneta', 'Tornado'];
public myHero = this.heroes[0];
public incrementCounter() {
if (this.currentCount < this.heroes.length -1) {
this.currentCount++;
}
else {
this.currentCount = 0;
}
}
}
It works! Every journey begins with its first step. Thanks for all your help!
I have a screen that uses jQuery tabs and was wondering if somehow I can keep the selected tab after a refresh of the page (JS .reload();)?
Any guidance appreciated.
https://github.com/carhartl/jquery-cookie
or
http://code.google.com/p/cookies/downloads/detail?name=jquery.cookies.2.2.0.js.zip&can=2&q=
Example for jquery.cookies.2.2.0.js
$(document).ready(function () {
var initialTabIndex = 0;
var jCookies = jQuery.cookies;
//alert('getting ' + jCookies.get("currentTab"));
if(jCookies.get("currentTab") != null){
initialTabIndex = jCookies.get("currentTab");
}
$('#tabs').tabs({
activate : function(e, ui) {
//alert('setting ' + ui.newTab.index());
jCookies.set("currentTab", ui.newTab.index().toString());
},
active : initialTabIndex
});
});
While the previous answer using cookies will certainly work, it is probably not the ideal solution given peoples aversions to accepting cookies. (it also means you would need to show a disclaimer on your site stating you use cookies, at least to EU visitors). I'd recommend avoiding using cookies where possible so your site remains functional if cookies are disabled/rejected.
A better way is to use the "hash" on the end of a URL.
Modify your tab links as follows:
<div id="UITabs">
<ul>
<li>Tab 1</li>
<li>Tab 2</li>
<li>Tab 3</li>
</ul>
<div id="Tab1"></div>
<div id="Tab2"></div>
<div id="Tab3"></div>
</div>
Then in your head, add the following javascript to ensure the hash is set when changing tabs, and get the hash on pageload and display the required tab:
$(document).ready(function () {
$(function () {
$("#UITabs").tabs();
$("#UITabs").bind("tabsshow", function (event, ui) {
location.hash = ui.newTab.find('a.ui-tabs-anchor').attr('href');
});
$(window).bind('hashchange', function (e) {
$("#UITabs").tabs("select", location.hash);
});
});
});
Here i am using a jquery datepicker from this sample http://dev.jtsage.com/jQM-DateBox2.
It is working fine but the problem is after clicking the submit button if there is any mandatory field validation error,the next time when i click the textbox jquery datepicker is not working means the script is not loading after submit click.it is throwing the error in firebug console like
TypeError: $(...).datebox is not a function
$('#txtstartdate').datebox('open');
Here is my code
$(document).ready(function () {
$('#txtstartdate').live('click', function () {
$('#txtstartdate').datebox('open');
$("#txtstartdate").datebox("option", {
mode: "calbox",
highDatesAlt: ["2011-11-09", "2011-11-10"],
highDates: ["2011-11-02", "2011-11-03"],
pickPageOAHighButtonTheme: "b"
});
});
});
and
#Html.TextBoxFor(m => m.StartDate, new { #name = "mydate", #id = "txtstartdate", style = "height:20px; font-size:10px;", data_role = "datebox", data_options = "{\"mode\":\"calbox\",\"useButton\": false}" })
Any suggestion?
as the firebug error suggest the browser could not find the function being used within the script can you make sure that the dependencies of the datebox is availiable after the submit call.
also try to send the dependencies with the view itself so that on every rendering of the view page at the client end it hold these js file with in it.
I am trying to create an app request using the Facebook C# SDK but it doesn't seem to work. Here is my code:
var fb = new FacebookClient(accessToken);
dynamic parameters = new ExpandoObject();
parameters.message = "Hello World!";
parameters.data = "Custom Data Here";
dynamic result = fb.Post("me/apprequests", parameters);
var id = result.id;
If I understand well it should show me a dialog box displaying a list of friends from which I can select but instead I don't see anything.
Would somebody also know how to get the list of facebook ids after the post has successfully occurred?
Thank you.
Jon
Here is source code for what you are trying to do, this is plain JavaScript SDK.
call following JavaScript function on your button click or similar:
function InviteFriends(){
var ResponseIds = FB.ui({
method : 'apprequests',
message: 'Your personal message herer',
},
function(ResponseIds ) {
console.log("IDS : " + ResponseIds.request_ids);
}
);}
Make sure you have following code in body tag
$
<div id="fb-root"> </div>
<script type="text/javascript" src="http://connect.facebook.net/en_GB/all.js"></script>
<div class="footer">
<br />
<script type="text/javascript">
FB.init({
appId: 'YOUR_APPID_HERE', //read your appid dynamically from codebehind C#
status: true, // check login status
cookie: true, // enable cookies to allow the server to access the session
xfbml: true // parse XFBML
});
</script>
</div>