How to bind to complex objects with radios and checkboxes in AngularJS? - data-binding

Say we have a set of projects exposed via the Project service:
{ id: '123', name: 'Yeoman', watchers: '1233', ... }
{ id: '123', name: 'Grunt', watchers: '4343', ... }
Then, we have a form to choose your favorite project:
Select favorite project:
%label.radio(ng-repeat="project in Project.query()")
%input(type="radio" ng-model="data.favoriteProject" value="{{project.id}}") {{project.name}}
This sets choices.favoriteProject to the id value of the chosen project. Often, we need to access the related object, not just the id:
John's favorite project:
{{Project.get(data.favoriteProject).name}}
What I'm looking for is a way to bind the radios and checkboxes straight to the object itself, not the id, so we could do
John's favorite project:
{{data.favoriteProject.name}}
instead. This is possible with select directive via ng-options, but how can we do this with radios and checkboxes? I'd still like to use the ids for matching instead of references, if possible.
To clarify, here's an example what I'm looking for
Select favorite project:
%label.radio(ng-repeat="project in Project.query()")
%input(type="radio" ng-model="data.favoriteProject" value="{{project}}" ng-match="id") {{project.name}}
It says: "Please bind data.favoriteProject to the actual project object and use the id to check if they match (instead of references)".

[Update]
I've completely changed my answer after discovering the ngValue directive, which appears to be undocumented. It allows you to bind objects instead of just strings as values for ngModel on the radio button inputs.
<label ng-repeat="project in projects">
<input type="radio" ng-model="data.favoriteProject"
ng-value="project">{{project.name}}</input>
</label>
<p>Your favorite project is {{data.favoriteProject.name}}.</p>
This uses references to check rather than just IDs, but I think in most cases, this is what people will be looking for. If you do very strictly only want to match based on IDs, you can use the [Old Answer], below, or even better, just create a function--e.g. projectById(projectId) that you can use for looking up a project based on its ID.
I've updated the JSFiddle to demonstrate: http://jsfiddle.net/BinaryMuse/pj2GR/
[Old Answer]
Perhaps you can utilize the ng-change attribute of the radio button directive to achieve what you want. Consider this HTML:
<p>Select your favorite project:</p>
<label ng-repeat="project in projects">
<input type="radio" ng-model="data.favoriteProjectId" value="{{project.id}}"
ng-change="data.favoriteProject = project">
{{project.name}}
</input>
</label>
<p>Your favorite project is {{data.favoriteProject.name}}.</p>
You could also call a function inside ng-change, for example setfavoriteProject(project)--but I did not do that here for simplicity's sake.
Here is a working jsFiddle to demonstrate the technique: http://jsfiddle.net/BinaryMuse/pj2GR/7/

No ng-change needed (and I'm not sure, if it is a good practise to write inline-code like this. On the other hand angulars directives are not too far from it theirselves). Why not just do something like this (works with ng-repeat as well):
Fiddle:
http://jsfiddle.net/JohannesJo/VeZxh/3/
Code:
<body ng-app="app">
<div ng-controller='controller'>
<input type = "radio" ng-model = "oSelected" value = "{{aData[0]}}">
<input type = "radio" ng-model = "oSelected" value = "{{aData[1]}}">
<div>test: {{oSelected}}</div>
</div>
</body>
app = angular.module('app',[]);
app.controller('controller', function($scope){
$scope.aData = [
{o:1},
{o:2}
];
$scope.oSelected = {};
});
Edit: I maybe should mention that this doesn't work for checkboxes, as value will either be true or false. Also a shared model will lead to all checkboxes either being checked or unchecked at the same time.

Related

Correct way to disable multiple Textarea / checkbox depends on the dropdown box's Value

I'm looking for an effective way to enable / disable multiple controls (textarea, checkbox) through dropbox. I.e. Selecting item A in dropbox will disable certain controls, while selecting item B in dropbox will disable some other controls. Codes on how I approach with disabling textbox:
HTML:
<template name="Gender">
<input disabled={{shouldBeDisabled}} class="input" type="text"/>
</template>
<template name="DoB">
<textarea rows="3" cols="27" disabled={{shouldBeDisabled}}>purpose</textarea>
</template>
js:
Template.registerHelper("shouldBeDisabled", function() {
return true
});
Question 1: Do we require a registerHelper function for each individual control? In the code above it seems like the registerhelper will either disable or enable both control as oppose to individual, but having multiple registerhelper seems redundant.
Question 2: How can we control the value in registerHelper via dropbox (i.e. select)? I can return the value from the dropbox, is building a switch inside registerhelper the correct way and how does it incorporate into question 1?
Question 3: Is there a build-in function to add visual effect on disabled controls? (i.e.grey out)
The way I have done this in the past w/ Meteor and Blaze is to setup a event listener on the dropdown that sets a reactive var/session variable that I then read in a helper. The helper would return the string "disabled" depending on the value.
For instance (this is from memory...I don't have access to Meteor right now, and I have switched over to React/Mantra):
Template.MyComponent.oncreated(function() {
this.isDropdownDisabled = new ReactiveVar();
});
Template.MyComponent.events({
'change #myDropdown'(event) {
this.isDropdownDisabled.set($('#myDropdown').val() == 'Selected' ? true : false);
}
});
Template.MyComponent.helpers({
isDropdownSelected() {
return this.isDropdownDisabled.get() == true ? '' : 'disabled';
}
});
<select id="myDropdown">
<option value="Not Selected">Not Selected</option>
<option value="Selected">Select Me</option>
</select>
<input id="myDynamicallyDisabledInput" type="textbox" name="dnyamic1" {{isDropdownSelected}} />
That should roughly be correct. Basic idea is that you use a reactive var to store the "state" of the dropdown value, flip the "state" when that value changes, and use a help in the inputs to determine if the disabled attribute needs to be set or not. Since helper functions are reactive by default, swapping the state var will cause the template to re-evaluate any time the dropdown value changes.
Anyone can feel free to edit this response to clean up any bad code above, as again I haven't used Blaze in some time and I did this all from memory.

How does `event.currentTarget.INPUT.value` give me an input value in a Meteor form submit handler?

I found example code to fetch values of text inputs from a submitted form in Meteor. I wrote my own version. Works great! Here's a snippet from my form submit event handler:
'submit form': function(event, template) {
event.preventDefault();
Assets.insert({
name: event.target.inputName.value,
location: event.target.inputLocation.value,
value: event.target.inputValue.value
});
}
I'm confused about event.target.playerName. What kind of object is it? Does Meteor set up that object for us? Or is this a jQuery thing? Or something else?
Is it any better/safer to use event.currentTarget? Sounds like nesting forms is not allowed, so it might not make any difference since there wouldn't be any way for it to "bubble up" given the 'submit form' event map key.
Crossposted here.
In that case, you're not using the template object but rather the plain jQ way. From a quick look at the page containing the example code, they use function(event) as opposed to function(event, template) (I prefer the latter, but that's a matter of taste). Here's how t make use of the template object.
Suppose your form look like this
<template name='createAccount'>
<form role="form">
<input placeholder="Your Name" name="name" type="text" />
<input placeholder="E-Mail Address" name="email" type="email" />
<input placeholder="Password" name="password" type="password" />
<div id="submitForm" class="outerButton">
(SOME BUTTON)
</div>
</form>
</template>
Here's pattern using template:
Template.createAccount.events({
'click #submitForm': function (event, template) {
var displayName = template.find("input[name=name]").value;
var eMailAddress = template.find("input[name=email]").value;
var password = template.find("input[name=password]").value;
< INSERT displayName, eMailAddress, password IN COLLECTION>
}
})
Pretty new to meteor myself so I could be completely wrong, but I believe your event target is just standard javascript, not meteor or jquery specific. I've been thinking of it as a shorthand for creating your own object.addEventListener(), so your playerName is going to be a child element of the form since it's the key of the object.
Imagine if it was setup something like form.addEventListnener('submit', function(e){ ... }) maybe makes it more familiar.
As for number 2, I wouldn't see why you couldn't use currentTarget if you needed to. I don't think it'd be any safer unless you really didn't know where the event might be coming from (perhaps creating a custom package?).
event.target returns the DOM element. In your case, it's the form element, and you can use named property to get its node, see the spec
In this case it's OK to use either target or currentTarget. In other examples when there is a 'nested' node, it might be better to use currentTarget.

Angular: How to bind to an entire object using ng-repeat

I'm just beginning to experiment in Angular, and confused about how best to approach binding using ng-repeat. I basically get the point about ng-repeat creating a child scope. My problem is much more basic :) For html like this:
<div ng-controller="swatchCtrl" class="swatch-panel">
Swatches
<ul>
<li ng-repeat="swatch in swatchArray" class="swatch">
<input
type="radio"
name="swatches"
ng-model="$parent.currentSwatch"
value="{{swatch}}"
>
<label class="swatch-label">
<div class="swatch-color" style="background-color: #{{swatch.hexvalue}};"></div
><span class="swatch-name">{{swatch.colorName}}</span>
</label>
</li>
</ul>
currentSwatch is:
<pre>{{currentSwatch | json}}</pre>
currentSwatchObj is:
<pre>{{currentSwatchObj | json}}</pre>
how do I tell this to fire??
swatchArray is:
<pre>{{swatchArray | json}}</pre>
</div>
and javascript like this:
function swatchCtrl($scope) {
$scope.swatchArray = [
{colorName:'Red', hexvalue: 'ff0000', selected: 'false'},
{colorName:'Green', hexvalue: '00ff00', selected: 'false'},
{colorName:'Blue', hexvalue: '0000ff', selected: 'false'}
];
$scope.currentSwatch = {};
}
http://jsfiddle.net/8VWnm/
I want to:
a) When the user clicks on a radio button, I want it to set both the colorName and the hexvalue properties of the currentSwatch object. Right now the binding seems to be giving me a stringified object from the array. How do watch the return of currentSwatch so I can parse it back to an available object? Simple, I know, but what am I missing?
b) When the user clicks on a radio button, I think I want that to set the value of the corresponding "selected" key in the original array to "true". Vice versa for unchecking. Let's say that only one swatch can ever be selected at a time in the palette. (I would like in theory to be able to iterate through the array later on, on the supposition that the different keys and values are likely to sometimes not be unique.)
This kinda stuff is super easy with jquery methods, but I'd like to learn the idiomatic angular way. Thanks in advance for any help.
http://jsfiddle.net/8VWnm/54/
Instead of listening to the ng-click event I would set the index of the selected element to a variable called "currentSwatchIndex"
<li ng-repeat="swatch in swatchArray" class="swatch">
<input
type="radio"
ng-model="$parent.currentSwatchIndex"
value="{{$index}}"
>
</li>
The you can $watch value changes of the currentSwatchIndex in your controller and set the selected swatch-Object and selection states in this $watch function:
$scope.$watch('currentSwatchIndex', function(newValue, oldValue) {
$scope.currentSwatchObj = $scope.swatchArray[newValue];
$scope.swatchArray[newValue].selected = true;
$scope.swatchArray[oldValue].selected = false;
});
Only knowing the currentSwatchIndex should be enough to identify the selected swatchObject. So probably you can get rid of the currentSwatchObj and the selected property of your swatchArray.
You can always get the selected swatch programmatically through a array access.
For future users that can come here to do the same in a select, you don't need use any index, the select must be done like this:
http://docs.angularjs.org/api/ng.directive:select

Orchard and master detail editing

I was reading http://www.orchardproject.net/docs/Creating-1-n-and-n-n-relations.ashx and could not get the idea, if it is possible to easily make master detail editing, to give you concrete example i've attached screenshot from wordpress:
So there is post and post contains set of custom fields, simple 1:N relationship, everything edited in one page - you can add/edit custom field without leaving post page.
May be someone saw similar example for Orchard on internet, or could shortly describe path to achieve this by code, would be really helpful (I hope not only for me, because this is quite common case I think).
This should be possible, although not in the most 'Orchardy' way.
I've not tested any of the below so it is probably full of mistakes - but maybe Bertrand or Pszmyd will be along later today to correct me :-)
As you have probably seen you can pass a view model to a view when creating a content shape in your editor driver:
protected override DriverResult Editor(CatPart part, dynamic shapeHelper)
{
// Driver for our cat editor
var viewModel = new CatViewModel
{
Cats = _catService.GetCats() // Cats is IEnumerable<Cat>
};
return ContentShape("Parts_CatPart_Edit",
() => shapeHelper.EditorTemplate(
TemplateName: "Parts/CatPart",
Model: viewModel,
Prefix: Prefix
));
}
So we can pass in a list of items, and render it in our view like so:
#foreach(var cat in Model.Cats)
{
<span class="cat">
<p>#cat.Name</p>
<a href="...">Delete Cat</p>
</span>
}
The problem here would be posting back changes to update the model. Orchard provides an override of the Editor method to handle the postback when a part is edited, and we can revive the viewmodel we passed in the previous method:
protected override DriverResult Editor(CatPart part, IUpdateModel updater, dynamic shapeHelper)
{
var viewModel = new CatViewModel();
if (updater.TryUpdateModel(viewModel, Prefix, null, null))
{
// Access stuff altered in the Cat view model, we can then update the CatPart with this info if needed.
}
}
This works really well for basic information like strings or integers. But I've never been able to get it working with (and not been sure if it is possible to do this with) dynamic lists which are edited on the client side.
One way around this would be to set up the buttons for the items on the N-end of the 1:N relationship such that they post back to an MVC controller. This controller can then update the model and redirect the client back to the editor they came from, showing the updated version of the record. This would require you to consistently set the HTML ID/Name property of elements you add on the client side so that they can be read when the POST request is made to your controller, or create seperate nested forms that submit directly to the contoller.
So your view might become:
#foreach(var cat in Model.Cats)
{
<form action="/My.Module/MyController/MyAction" method="POST">
<input type="hidden" name="cat-id" value="#cat.Id" />
<span class="cat">
<p>#cat.Name</p>
<input type="submit" name="delete" value="Delete Cat" />
</span>
</form>
}
<form action="/My.Module/MyController/AddItem" method="POST">
<input type="hidden" name="part-id" value="<relevant identifier>" />
<input type="submit" name="add" value="Add Cat" />
</form>
Another possibility would be to create a controller that can return the relevant data as XML/JSON and implement this all on the client side with Javascript.
You may need to do some hacking to get this to work on the editor for new records (think creating a content item vs. creating one) as the content item (and all it's parts) don't exist yet.
I hope this all makes sense, let me know if you have any questions :-)

Way to update css properties on-the-fly based on form content?

I have a form on a website, in which one of the inputs is to be used to enter hexadecimal colour codes to be entered into a database.
Is there any way for the page to dynamically update itself so that if the user changes the value from "000000" to "ffffff", the "colour" CSS property of the input box will change immediately, without a page reload?
Not without Javascript.
With Javascript, however...
<input type='text' name='color' id='color'>
And then:
var color = document.getElementById('color');
color.onchange = function() {
color.style.color = '#' + this.value;
}
If you are going to go the Javascript route, though, you might as well go all out and give them a color picker. There are plenty of good ones.
CSS properties have corresponding entries in the HTML DOM, which can be modified through Javascript.
This list is somewhat out of date, but it gives you some common CSS pieces and their corresponding DOM property names.
Granted, a JS lib like like jQuery makes this easier...
You can use Javascript to achieve that.
As an example:
Your HTML:
<input type="text" id="test" />
Your JS:
var test = document.getElementById('test');
test.onchange = function(){
test.style.color = this.value;
};
But this doesn't check the user's input (So you would have to extend it).

Resources