Angular Breeze one-many-one example with checkbox - angularjs

I have implemented domain models using Breeze's recommended one-many-one approach, and would like to assign these relationship using a checkbox approach. I'm using Angular for my data-binding, so this would be via the ngChecked directive.
I was wondering if anyone has attempted something similar and is able to post code snippets for getting, creating and deleting the one-many-one relationship, both at a controller and data service level.
I'm about to start on the requirement, and will gladly post my example as response to my question for those interested.
I've scanned through the Breeze samples, but couldn't find one fulfilling this requirement.
Many thanks!
Adding on some of my markup and scripts for this question, as I just cannot make sense of how I'm meant to approach this. Just to explain my use-case a bit. I provide users the capability at the top ofa page to create a list of "channels". Users can then also create a number of "business units". For each business unit, all channels get displayed and users can select which channels apply for each business unit.
Markup...
...
<div class="form-group"> /* portion of markup for adding channel */
<label class="control-label">New Interaction Channel</label>
<input class="form-control" placeholder="Channel Name..." data-ng-model="vm.newChannel.name" />
</div>
<div class="form-group">
<button class="btn btn-primary" data-ng-disabled="!vm.newChannel.name"
data-ng-click="vm.addChannel(vm.newChannel, $parent.vm.dboardConfig)">
Add Channel</button>
....
<accordion>
<accordion-group data-ng-repeat="bu in vm.busUnits"> /* business units listed in accordion groups */
<accordion-heading>
{{bu.name}}
...
</accordion-heading>
<h5>Channels</h5>
<div class="well well-sm panel-body">
/* this is where I start getting stuck. */
/* just not sure how to allocate the item viewmodel from */
/* Ward's example in my scenario */
<div data-ng-repeat="buc in bu.buChannels">
<div class="col-md-6">
<input type="checkbox" data-ng-checked="?????"
data-ng-model="buc.isSelected"/>
{{buc.name}}
</div>
...
and in my controller...
function getBusUnits() {
...
.then(function(data){
vm.busUnits = data;
vm.busUnits.forEach(function(bu){
getBusUnitChannels(bu);
});
});
}
function getBusUnitChannels(busUnit) {
datacontextSvc.dboardConfigs.getBusUnitChannelsById(busUnit.id)
.then(function (data) {
busUnit.busUnitChannelList = data;
});
busUnit.buChannels = [];
vm.channels.forEach(function (channel) {
busUnit.buChannels.push(channel);
// how do I assign the isSelected for each buChannel?
// how do I associate each buChannel with the BusUnitChannel obtained via breeze?
});
Am I going even vaguely in the right direction? I haven't event dealt with saving back to the server yet, I'd just like to be able to populate my lists first :-)

I've written a plunker to demonstrate the many-to-many checkbox technique I described previously. I'm hopeful it provides the essential insights for your case.

Ok, this is the solution I've come up with. I'd really appreciate some thoughts on my approach and whether or not its the most efficient (or even correct) approach..
Markup...
...
<div class="form-group"> /* portion of markup for adding channel */
<label class="control-label">New Interaction Channel</label>
<input class="form-control" placeholder="Channel Name..." data-ng-model="vm.newChannel.name" />
</div>
<div class="form-group">
<button class="btn btn-primary" data-ng-disabled="!vm.newChannel.name"
data-ng-click="vm.addChannel(vm.newChannel, $parent.vm.dboardConfig)">
Add Channel</button>
....
<accordion>
<accordion-group data-ng-repeat="bu in vm.busUnits"> /* business units listed in accordion groups */
<accordion-heading>
{{bu.name}}
...
</accordion-heading>
<h5>Channels</h5>
<div class="well well-sm panel-body">
<div data-ng-repeat="buc in bu.buChannels">
<div class="col-md-6">
<input type="checkbox" data-ng-model="buc.isSelected"/>
{{buc.name}}
</div>
...
and in my controller...
function getBusUnits() {
...
.then(function(data){
vm.busUnits = data;
vm.busUnits.forEach(function(bu){
getBusUnitChannels(bu);
});
});
}
function getBusUnitChannels(busUnit) {
datacontextSvc.dboardConfigs.getBusUnitChannelsById(busUnit.id)
.then(function (data) {
busUnit.busUnitChannelsList = data;
busUnit.buChannels = [];
vm.channels.forEach(function (channel) {
busUnit.buChannels.push(channel);
});
busUnit.busUnitChannelsList.forEach(function (buc) {
busUnit.buChannels.forEach(function (buCh) {
if (buc.channelId === buCh.id) {
buCh.buChannel = buc;
buCh.isSelected = true;
} else {
buCh.isSelected = false;
}
});
});
});
}

Related

Angular - Binding data to dropdownlist never clears out previous items on updating (keeps old items plus add new)

Hopefully a simple Angular binding question.
I have a TextBox on my Angular 4 [x.component.html] that has an (input) event tied to it, so each time the user types in a character into the textbox it reaches out to a method in the [x.component.ts] typescript file - which then retrieves data from a service to populate a local array of data in the typescript file - which is bound to a dropdownlist on the [x.component.html]. This process functionally works fine.
However the problem I am having is that when it updates the data in the local array, it is not clearing out the previous data in the dropdown, so the dropdown list ends up getting populated with lots of duplicate data. Has anyone experienced this same problem before? The local array of items seems to have the correct number of items in it, it just seems to keep populating the dropdownlist with the items without ever clearing the previous.
Thanks so much for your help.
Please note I have cut the code down to only the relevant parts for brevity.
[x.component.ts]
export class CreateComponent {
testService: TestService;
valueList: testValue[];
constructor(testService: TestService) {
this.testService = TestService;
}
ngOnInit() {
}
FieldChanged(event: any, controlNameToPopulate: string){
if (event.target.value.length >= 9) {
this.testService.getRecords(event.target.value).subscribe((data: any[]) => {
if (data.length != 0){
document.getElementById(controlNameToPopulate).hidden = false;
//this.valueList.slice();
this.valueList = data;
}
});
}
}
}
[x.component.html]
<div class="row">
<div class="col-md-6">
<nb-card>
<nb-card-header>Testing with Updating dropdownlist</nb-card-header>
<nb-card-body>
<form [formGroup]="formGrp">
<div class="form-group row">
<label class="col-sm-3 col-form-label">Quick search</label>
<div class="col-sm-9 input-group">
<input type="text" class="form-control input-sm" style="width: 50%" id="txtbx_ValTest" formControlName="testVal" (change)="FieldChanged($event, 'ddValuesToUpdate')">
<select id="ddValuesToUpdate" class="btn btn-secondary dropdown-toggle" style="width: 50%" hidden>
<option *ngFor="let val of valueList" [value]="val.id">{{val.id}}</option>
</select>
</div>
</div>
<div class="form-group row">
<div class="offset-sm-3 col-sm-9">
<button type="submit" class="btn btn-success pull-right">Add</button>
</div>
</div>
</form>
</nb-card-body>
</nb-card>
</div>
</div>

Submitting user feedback data to Elasticsearch

I'm trying to use this angular bootstrap feedback form to collect user feedback data and index the information to Elasticsearch (to make it searchable).
So in my searchService for Elasticsearch, I simply did:
this.userSearchFeedback = function (userFeedbackForm) {
esClient.index({
index: 'searchfeedback-index1',
type: 'general',
opType: 'index',
refresh: true,
body: {
fields: ['comments', 'email']
}
}).then(function(es_return) {
console.log('success');
}, function(error) {
console.log('error');
});
};
userFeedbackForm is the name of the form, which looks like this:
<div class="row">
<div class="col-xs-12">
<div class="alert alert-info" role="alert">
We would love to hear your thoughts and suggestions on what we get right and wrong - it helps us improve our services to you. Your feedback is appreciated and helps us get better!
</div>
</div>
</div>
<div class="row">
<div class="col-lg-6">
<!-- DESCRIPTION -->
<div class="form-group">
<pre>Model: {{comments | json}}</pre>
<label for="description_field" class="control-label">Comment:</label>
<textarea class="form-control" placeholder="Please describe your issue or share your feedback!" name="comments" ng-model="comments" style="min-height: 165px;"></textarea>
</div>
<!-- /DESCRIPTION -->
<!-- EMAIL -->
<div class="form-group">
<pre>Model: {{email | json}}</pre>
<label for="email_field" class="control-label">Email:</label>
<input type="text" class="form-control" placeholder="Email" name="email" ng-model="email" />
</div>
<!-- /EMAIL -->
</div>
I've added ng-models for both inputs and added those in my controller
//initialize feedback form
$scope.feedback = {
comments: '',
email: ''
};
The module already comes with a function that is called from ng-click on the form template...
function submitButtonPressed(form) {
console.log("Submit button pressed");
console.log(form);
console.log($scope.feedback.comments);//added
console.log($scope.feedback.email);//added
and that is where I'm stuck.
What else do I need to do with the function in order for it to call the userSearchFeedback() in my searchService so that the form data can be indexed to ES?
After just trial and error with the above posted. I decided to look to see if there was something else available. There is and its amazingly simple to implement. Just add the url to submit to and your done! Had it up and running in just a few minutes (once I realized I had to supply a url).
HTH anyone.

angular submitting only form data not whole model

I'm submitting form data to my API but my model contains my whole object. I should really only submit the updates, correct?
So,
my item is retrieved by Id, which I don't need to show to the user (hidden field).
I'll show the user the title of item so they know what they're editing, but I won't make it editable (read-only).
I'll expose description so the user can edit it and save.
(Imagine a larger form - a half dozen more read-only fields and a half dozen more editable fields.)
Correct me if I'm wrong but, when sending to my API, I should not be sending the entire item object. The db already has title, why put it in the payload if its just going to be thrown away?
So, I should really only be sending the values of the editable fields.
I'm trying to figure out the proper way of separating the model from the form data so I submit the latter, not the former. If this is not best practice, please correct me.
Controller:
.controller('editItemController', ['$stateParams',
function($stateParams) {
var vm = this;
vm.getItem = function () {
$http.get('/api/Items/' + $stateParams.id).then(function (response) {
vm.item = response.data;
});
};
vm.saveChanges = function () {
vm.submitted = true;
if (vm.detailsForm.$valid) {
$http.put('/api/Items/' + $stateParams.id, vm.item).then(function (response) {
});
}
};
vm.getItem();
}
View:
<form name="itemVm.detailsForm" ng-controllerAs="itemVm">
<input type="hidden" name="Id" ng-model="itemVm.item.Id" />
<div class="form-group">
<label for="title" class="control-label col-xs-3">Title:</label>
<div class="col-xs-9">
<input type="text" class="form-control" readonly="readonly" id="title" value="{{ itemVm.item.Title }}">
</div>
</div>
<div class="form-group">
<label for="description" class="control-label col-xs-3">Description:</label>
<div class="col-xs-9">
<input type="text" class="form-control" name="description" ng-model="itemVm.item.Description" required>
</div>
</div>
<button ng-click="itemVm.saveChanges()">Save Changes</button>
</form>
For a standard REST API, you should be sending the entire object. The entire idea behind REST is that you transfer representations of the state of an object, hence representational state transfer. As such, it should be a complete representation, not a portion.
There are multiple ways to circumvent this if you'd really like, and most have to do with the back end, not the front end.
Either create an ad hoc endpoint to take in the one parameter and update the persistent object accordingly. (Not recommended).
If you want to send partial data (which I think is a good idea some times depending on the size of the object), you should handle that on the back end accordingly.
See here.

Update Models after save without refreshing

I'm building a list in my app, which users can add to. Once they add their item and hit the save button, I would like the item they added to appear instantly on the list, and for the input box to clear. Here is my save function in my controller. It works fine when I reload the page but I think there is a way to bind by updating the model through the database. Thank you!
export class QuestionDetailsController {
public question;
public answer;
public id;
public save() {
this.answerService.save(this.answer, this.question.id).then(() => { this.$location.path("$location.path") });//how to update models?
}
constructor(
private answerService: MyApp.Services.AnswerService, private $location: angular.ILocationService, private questionService: MyApp.Services.QuestionService, $routeParams: ng.route.IRouteParamsService)
{
this.question = this.questionService.get($routeParams["id"])
}
And now HTML page
<!--<legend id="qdLegend">{{controller.question.title}}</legend>-->
<h2 id="qdLegend">{{controller.question.title}}</h2>
<hr id="qdhr" />
<div id="questionDiv">
<div class="col-md-7" id="questionBody">
<p>{{controller.question.userQuestion}}</p><br /><br />
</div>
</div>
<div class="col-md-7">
<div id="answerDiv" ng-repeat="a in controller.question.answers">
{{a.userAnswer}} --answered by {{a.userName}}
</div>
</div>
<form ng-submit="controller.save()">
<div>
<div class="form-group col-md-7" id="answerBox">
<label class="control-label" for="textarea">Your Answer</label>
<textarea ng-model="controller.answer.userAnswer" class="form-control" id="textarea1" name="textarea"></textarea>
</div>
<div class="form-group">
<label class="col-md-4 control-label" for="button1id"></label>
<div class="col-md-8" id="detailsButtons">
<button type="submit" id="button1id" name="Save" class="btn btn-success">Save</button>
<button id="button2id" name="button2id" class="btn btn-danger">Cancel</button>
</div>
</div>
</div>
</form>
Okay, I don't have a running typescript compiler at the moment, so I'll give you some hints and some untested code here.
public save() {
this.answerService.save(this.answer, this.question.id)
.then(() => {
this.question.answers.push(angular.copy(this.answer));
this.answer = {};
});
}
When the service returns succes you add the answer the the questions list of answers. I use angular.copy() to break the references so you can add multiple answers with no references issues.
After this I just overwrite the object with an empty one.
This is fairly simple. AngularJS docs have an example in the same style.
Here is a simplified plunker showing the action with a javascript controller. This should be no problem for you to get a hold of how it should be done.
As a note to the question, please supply minified examples to make it faster to understand your question.

Ng-model does not update view

I have searched thoroughly everywhere and can't seem to find the solution to my problem.
I am trying to make a form to fill in to create new task object onto projects on the website like a scrum backlog or something in Angular and angular-ui.
I use Angular-UI for typeahead functionality for when adding team members to a task, so available members on a project pop-up. I have seen guys with similar problems, but nothing seems to solve it for me. Below is the HTML and the controller. Sorry for the long markup, but I suspect it has to do something with nesting the controllers and mixing the scopes, so I'm including everything relevant.
<div ng-controller="SubmitCreateTaskController">
<div class="modal fade" id="taskModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">
<span aria-hidden="true">×</span>
<span class="sr-only">Close</span>
</button>
<h4 class="modal-title" id="myModalLabel">New Task</h4>
</div>
<div class="modal-body">
<form role="form" method="post" name="create-task">
<div class="form-group">
<label for="taskname">Task Name</label>
<input type="text" class="form-control" id="taskname" ng-model="taskForm.taskName" placeholder="Task Name">
</div>
<div class="form-group">
<label for="taskdesc">Description</label>
<textarea class="form-control" id="taskdesc" size="3" ng-model="taskForm.taskDescription" placeholder="Enter a short description here..." rows="2"></textarea>
</div>
<div class="form-group">
<label for="assigneddate">Assigned Date</label>
<input type="text" class="form-control" id="assigneddate" ng-model="taskForm.assignedDate">
</div>
<div ng-controller="TypeAheadController">
<div class="form-group">
<label for="contributors">Add Contributors</label>
<input id="contributors" type="text" class="form-control" ng-model="contrib.selected" typeahead="member for member in contrib.stream_members | filter:$viewValue"
typeahead-editable="false" typeahead-on-select="contrib.onSelect($item)">
</div>
<div class="form-group">
<label for="users">Contributors:</label>
<textarea class="form-control" disabled="disabled" id="users" ng-model="contrib.entered" rows="1"></textarea>
</div>
</div>
<div class="form-group">
<label for="taskcomments">Comments</label>
<textarea class="form-control" id="taskcomments" ng-model="taskForm.comment" placeholder="Comments" rows="2"></textarea>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary" ng-click="taskForm.submit()">Add Task</button>
<button type="button" class="btn btn-danger" data-dismiss="modal">Cancel</button>
</div>
</form>
</div>
</div>
</div>
</div>
Controllers:
.controller('SubmitCreateTaskController', ['$scope', '$http', function($scope, $http) {
$scope.taskForm = {};
$scope.taskForm.taskName = '';
$scope.taskForm.taskDescription = '';
$scope.taskForm.assignedDate = new Date();
$scope.taskForm.contributors = [];
$scope.taskForm.comment = '';
$scope.taskForm.submit = function(item, event) {
var formData = {name: $scope.taskForm.taskName,
description: $scope.taskForm.taskDescription,
status: 'open',
assigned: $scope.taskForm.assignedDate,
completed: null,
contributors: $scope.taskForm.contributors,
comments: [{body: $scope.taskForm.comment,
user: 'RACHE User',
date: new Date()
}]
};
var postURL = '../create_task/' + $scope.stream_name;
$http.post(postURL, formData)
.success(function(){
taskForm = {}; // empty the form of previous input values
alert('New Task Created for ' + $scope.stream_name);
})
.error(function(res) {
alert(res.data);
});
};
}])
.controller('TypeAheadController', ['$scope', '$http', function($scope, $http) {
$scope.contrib = {};
$scope.contrib.selected = '';
$scope.contrib.stream_members = undefined;
$scope.contrib.entered = [];
$http.get('/stream_members/' + $scope.stream_name)
.then(function(res) {
$scope.contrib.stream_members = res.data.stream_members;
});
$scope.contrib.onSelect = function($item) {
$scope.contrib.selected = '';
$scope.contrib.entered.push($item);
console.log($scope.contrib.entered);
};
This last bit is the important part in 'TypeAheadController'. I am trying to add the poped-up team members to a textarea just below which would be sent to the DB later. That is why I have bound the actual typeahead input box to 'contrib.select' and the "display/post" box to 'contrib.entered', so the search can be continued after adding one member, the search box is cleared, selected member is added to box below, new search can begin. In the mark up I call the last (onSelect) function in the typeahead-on-select callback. This so that I can clear the input box and another member can be easily added without deleting manually. IN this function the entered member should be appended to the array that is bound to below "display/post" box. This happens, since the console log shows good value, the search/input field gets cleared, so the function gets called as well.
The view does not get updated with the updated array bound to display box however. I have tried EVERYTHING. I have googled around and found it here that dot notation needs to be used as Angular can't update with primitives, but this hasn't helped either.
Everything works fine, the logic is good, values are good when I print them out, pop-up works and clears up as expected, but The damn textarea under it does not get updated.
Any suggestions? I have been stuck on this for almost a day now and I am really frustrated by this. I would be ever so greatfull for any help!
Thanks guys in advance!
Textarea do use the value attribute, your code isn't working because contrib. entered isn't between the tags, but because you can not bind the textarea to an array (it needs to bind to a string).
look at this plunker, if you do this in your html:
<textarea class="form-control" disabled="disabled" id="users" rows="1" ng-model="contrib.enteredString"></textarea>
and add this line at the end of your typeahead controller:
$scope.contrib.enteredString = $scope.contrib.entered.toString();
then the text area will update
Textareas don't use the ng-value as their value is contained within the tag. Remove the ng-model and put the value between the tags.
<textarea class="form-control" disabled="disabled" id="users" rows="1">{{contrib.entered}}</textarea>

Resources