Can't pass ViewBag data to AngularJS - angularjs

I have a controller:
public ActionResult Edit(int id) {
ViewBag.IsComplete = false;
return View(dbContext.Users.Where(user => user.Id == id))
}
and the corresponding view:
#model User
#{
ViewBag.Title = "Edit User";
Layout = "~/Views/Shared/_PopupLayout.cshtml";
}
<p>#ViewBag.IsComplete</p>
<div ng-init="init(isComplete: #ViewBag.IsComplete)" />
The first instance of ViewBag.IsComplete (in the <p> tag) emits the correct value. The second emits null. Can anyone tell me what is going on here and how to fix it so the second #ViewBag.IsComplete emits the same as the first?

Use #Html.Raw(ViewBag.IsComplete) in your ng-init binding.

Use #Html.Raw which returns html markup which is not encoded.
<div ng-init="init(isComplete: #Html.Raw(ViewBag.IsComplete))" />

You need to convert the bool value to string, you can either use #Html.Raw() as suggested in other answers, or just do #ViewBag.IsComplete.ToString().
But, when you convert a bool value to string in C#, it will be converted to either "True" or "False", but Javascript doesn't recognize those as bool values. So, you'll have to convert it to lowercase as well. Here's what you need to do:
<div ng-init="init(isComplete: #ViewBag.IsComplete.ToString().ToLower())" />

In concept:
<div ng-init="init(isComplete: '#ViewBag.IsString')" />
Note the ViewBag.IsString, is replaced directly in the Html,
So if it's a string ViewBag.IsString= "Mystring"
html is:
<div ng-init="init(isComplete: 'Mystring')" />
in your case :
<div ng-init="init(isComplete: true)" /> //might give error
try : <div ng-init="init(#ViewBag.IsComplete)" />
might not work since it replaces it with true, else pass string or json

Related

Dynamic checkbox from list with Thymeleaf

I try to save the values from dynamically created checkboxes:
<div>
<ul>
<li th:each="item, stat : *{users}">
<input type="checkbox" th:field="*{users[__${stat.index}__]}" th:value="${item}" />
<label th:text="${item}"></label>
</li>
</ul>
</div>
The controller provides the String items as follwing:
public List<String> getUsers() {
return users;
}
And the setter for the Strings is:
public void setUsers(final String[] users) {
for (final String string : users) {
System.out.println(string);
}
}
The values are correct shown in the html page. But when i click save button, and the setter is called, the values are empty. What can i do, where is the problem?
Any help would appreciate.
Please check out section about handlin multi-value checkboxes in Tutorial: Thymeleaf + Spring.
You should provide some model attribute (of type List<String>) containing all users possible to select. Let's call it selectableUsers.
Then it can collaborate with your form-backing bean (that one containing users) in a following manner:
<div>
<ul>
<li th:each="item : ${selectableUsers}">
<input type="checkbox" th:field="*{users}" th:value="${item}" />
<label th:for="${#ids.prev('users')}" th:text="${item}"></label>
</li>
</ul>
</div>
Note I think that getter and setter for a field should handle the same type, but they don't (getter returns List<String> however setter consumes String[])
What you are trying to do looks logical, but it does not work that way.
If you did not get it resolved you can do this instead:
In relevant method of your controller you can add list of titles for your checkboxes:
List<String> allUsers = Arrays.asList("abc","xyz"); // OR generate list dynamically
model.addAttribute("selectableUsers", allUsers);
Or add it to ModelAndView if that is what you are using.
Change your html to what was suggested by #Jakub Ch.
Change your getter and setter methods as follows:
private String users;
...
public String getUsers() {
return this.users;
}
public void setUsers(String users) {
this.users = users;
}
Then 'users' field will contain comma separated String values or their id numbers ( depending on how you set it up) indicating selected checkboxes. Then you can convert String values to array using code like below or if id numbers are stored get String values from your ArrayList.
public List<String> getStrings() {
return Arrays.asList(strings.split(","));
}
Hope it helps.

ng-class with multiple options

I'm fetching data using a REST API and one of the attributes returned can be any of 3 options;
The options are; Negative, Warning, Success.
I want to set a class using ng-class based on the value returned;
I'm able to do this but only for one;
Code below;
<div class="alert" ng-class="restaurantNotice(restaurant[0].notice)" ng-if="restaurant[0].notice.length">
<div class="alert-inner inner-large" ng-bind="restaurant[0].notice"></div>
</div>
$scope.restaurantNotice = function(a) {
return (a = Negative) ? 'negative' : '';
};
As you can see, if a = Negative then a class negative is applied else nothing is applied.
Now I want to check for all three options and apply 3 different classes respectively
Instead of function in ng-class, use object
ng-class="{'negative' : restaurant[0].notice == 'Negative', 'warning' : restaurant[0].notice == 'Warning', 'success' : restaurant[0].notice == 'Success'}"
You can try
<div class="alert" ng-class="{'negative' : restaurant[0].notice.length, 'warning' : restaurant[1].notice.length, 'success' : restaurant[2].notice.length}">
<div class="alert-inner inner-large" ng-bind="restaurant[0].notice"></div>
</div>
duplicate of Adding multiple class using ng-class
In this way:
<p ng-class="{a: deleted, b: important, c: error}">Example</p>
if a is true, apply class deleted, and so on.
If you are just going to set the same notice type (Negative, Warning, Success) as class then just convert to lower case and return from the function instead of putting conditions.
$scope.restaurantNotice = function(a) {
return a.toLowerCase();
};
OR
<div class="alert" class="{{restaurant[0].notice | lowercase}}" ng-if="restaurant[0].notice.length">

ngClass string binding expression with class map

Is it possible to use ngClass with an expression AND a class map? I want to conditionally add a class based on the existence of a variable as well as use that variable in the expression that creates the class.
For instance, if isActive() is true and getStatus() returns "valid" I want the class list to be "element element--active element--valid". If getStatus() returns undefined I want the class list to be "element element--active".
<div
class="element"
ng-class="{
'element--active': ctrl.isActive(),
'element--{{ ctrl.getStatus() }}': ctrl.getStatus()
}"></div>
Doesn't seem to work.
<div
class="element element--{{ctrl.getStatus()}}"
ng-class="{
'element--active': ctrl.isActive()
}"></div>
Works but then there's an extra hanging "element--" if getStatus() returns undefined.
Do I have to add a method in my controller to handle the class generation?
i'd suggest to make just one function call to get the classes. It will make it cleaner and have the class logic in one place.
In your controller:
this.getElementStatus = function(){
var rules = {active:this.isActive()}; //Prefix with element-- {element--active:}
rules[this.getStatus()] = true; //Prefix with element--, rules['element--' + this.getStatus()] = true
return rules;
}
and your view would just be:
<div
class="element"
ng-class="ctrl.getElementStatus()"></div>
It seems like your element-- is redundant with the rule instead make use of cascadeability(CSS) property. and define rules as :
Example:
.element.active{ /*....*/ }
.element.success {/*...*/}
.element.error{/*...*/}
This will help in maintenance, gets more verbose and get to the natural way of adding css rules and could remove these kind of complexities from the view.
You could as well do:
<div class="element"
ng-class="{'active': ctrl.isActive(), '{{ctrl.getStatus()}}':true}"
or :
<div class="element"
ng-class="[ctrl.isActive() ? 'active' : '', ctrl.getStatus()]"
If you don't mind getting a true added as a rule(should not affect anything anyways) then,
<div class="element"
ng-class="[!ctrl.isActive() || 'element--active' , 'element--' + ctrl.getStatus()]">
You can use class and ng-class map on the same element. But since your class name is dynamic you will have to something like this.
<div
ng-class="'element '
+ (ctrl.isActive() ? ' element--active' : '')
+ (ctrl.getStatus() ? ' element--' + ctrl.getStatus() : '')"></div>

Dynamic attribute in ReactJS

I want to dynamically include/omit the disabled attribute on a button element. I have seen plenty of examples of dynamic attribute values, but not of attributes themselves. I have the following render function:
render: function() {
var maybeDisabled = AppStore.cartIsEmpty() ? "disabled" : "";
return <button {maybeDisabled}>Clear cart</button>
}
This throws a parse error because of the "{" character. How can I include/omit the disabled attribute based on the (boolean) result of AppStore.cartIsEmpty()?
The cleanest way to add optional attributes (including disabled and others you might want to use) is currently to use JSX spread attributes:
var Hello = React.createClass({
render: function() {
var opts = {};
if (this.props.disabled) {
opts['disabled'] = 'disabled';
}
return <button {...opts}>Hello {this.props.name}</button>;
}
});
React.render((<div><Hello name="Disabled" disabled='true' />
<Hello name="Enabled" />
</div>)
, document.getElementById('container'));
By using spread attributes, you can dynamically add (or override) whatever attributes you'd like by using a javascript object instance. In my example above, the code creates a disabled key (with a disabled value in this case) when the disabled property is passed to the Hello component instance.
If you only want to use disabled though, this answer works well.
I'm using React 16 and this works for me (where bool is your test):
<fieldset {...(bool && {disabled:true})}>
Basically, based on the test (bool) you return an object with the conditional attributes or you don't.
Also, if you need to add or omit multiple attributes you can do this:
<fieldset {...(bool && {disabled:true, something:'123'})}>
For more elaborate attribute managed I suggest you prefab the object with (or without) the attributes outside of JSX.
You can pass a boolean to the disabled attribute.
render: function() {
return <button disabled={AppStore.cartIsEmpty()}>Clear cart</button>
}
function Test() {
return (
<div>
<button disabled={false}>Clear cart</button>
<button disabled={true}>Clear cart</button>
</div>
);
}
ReactDOM.render(<Test />, document.querySelector("#test-container"));
console.log(Array.from(document.querySelectorAll("button")));
<script crossorigin src="https://unpkg.com/react#17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#17/umd/react-dom.development.js"></script>
<div id="test-container"></div>
Far cleaner than the accepted solution is the solution which AnilRedshift mentioned, but which I'll expand on.
Simply put, HTML attributes have a name and a value. As a shorthand, you can use the name only for "disabled", "multiple", etc. But the longhand version still works, and allows for React to work in it's preferred way.
disabled={disabled ? 'disabled' : undefined} is the most legible solution.
The version I used was:
<button disabled={disabled ? 'disabled' : undefined}>
Click me (or dont)
</button>
More cleaner way of doing dynamic attributes which works for any attributes is
function dynamicAttributes(attribute, value){
var opts = {};
if(typeof value !== 'undefined' && value !== null) {
opts['"'+attribute+'"'] = value;
return opts;
}
return false;
};
Call in your react component like following
<ReactComponent {...dynamicAttributes("disabled",false)}
{...dynamicAttributes("data-url",dataURL)}
{...dynamicAttributes("data-modal",true)} />
Tips :
You could have dynamicAttributes function in a common place/utilities and
import it to use it across all components
you could pass value as null to not render dynamic attribute
A simple and clean way of doing it
<button {...disabled && {disabled: true}}>Clear cart</button>
disabled should come from props like this
<MyComponent disabled />
You can find something similar to this at the documentation.
https://facebook.github.io/react/docs/transferring-props.html
In your case could be something like this
function MyComponent(props) {
let { disabled, ...attrs } = props;
if (disabled) {
// thus, it will has the disabled attribute only if it
// is disabled
attrs = {
...attrs,
disabled: true
}
};
return (
<button {...attrs}>MyLabel</button>
)
}
This code is using ES6, but I thing you got the idea.
This is cool because you can pass many others attributes to this component and it will still work.
First you can simply check
<button disabled={true}>Button 1</button>
<button disabled={false}>Button 2</button>
Note: **disabled value is not String, it should be Boolean.
Now for dynamic. You can simply write
<button type="button" disabled={disable}
onClick={()=>this.setSelectValue('sms')} >{this.state.sms}</button>
As you can see I am using disabled property and in curly brackets it can be local variable/state var.
The variable disable contains values true/false.
In case others come here for attributes other than disabled, e.g., custom attributes like data-attr, one can assign empty string "" as the value in the object to be spread to eliminate the value of the attribute. With the attribute name only available on the resulted HTML.
For example:
<div {...(trueOrFalse && { [`data-attr`]: "" })}></div>
Furthermore, if you wish the name of the attribute being dynamic too. Due to template strings support string interpolation, you can put state into it to make the name of attribute dynamic.
<div {...(trueOrFalse && { [`${state}`]: "" })}></div>
This could work, problem with disabled is one could not simply set boolean for it.
if(AppStore.cartIsEmpty()){
return "<button disabled>Clear cart</button>"
}
else
{
return "<button>Clear cart</button>"
}

How to pass a current loop value to class method in Tapestry?

I want to dynamically show available menus of links to pages depending on which type of user is logged in using Tapestry.
Part of my code in Layout.tml looks like this:
<div class="header">
<t:if t:test="userLoggedIn">
<div class="menu">
<ul>
<t:loop t:type="loop" source="pageNames" value="pageName" class="prop:classForPageName">
<t:if t:test="isUserAllowedOnPage('pageName')">
<li>
<t:pagelink page="prop:pageName.name">${pageName.displayName}</t:pagelink>
</li>
</t:if>
</t:loop>
</ul>
</div>
</t:if>
<div style="clear:both;"></div>
</div>
In my Layout.java I have a following method:
public boolean isUserAllowedOnPage(String pageName) {
// My logic here, returns either true or false
}
The problem is, I do not know how to pass the actual page name parameter to isUserAllowedOnPage(String pageName) method, because with the following line of tml code
"isUserAllowedOnPage('pageName')"
I pass an actual string, "pageName" instead of one of the desired values (for example, "Index", "About", "Contacts"...).
Your loop specifies value="pageName" which means that tapestry will update the pageName property in your page each time it iterates through the loop. So, you don't need to pass it to a method since it's already set each time you invoke the method.
You could just do the following:
TML
<t:loop source="pageNames" value="pageName">
<t:if t:test="userAllowedOnPage">
...
</t:if>
</t:loop>
Java
#Property
private List<String> pageNames;
#Property
private String pageName;
...
public boolean isUserAllowedOnPage() {
// some calculation based on pageName
}
You can pass value to method without quotes as if you write this expression in java code:
<t:if t:test="isUserAllowedOnPage(pageName)">
</t:if>
Or:
<t:if t:test="isUserAllowedOnPage(getPageName())">
</t:if>

Resources