Angular - using the same form to Create and Update - javascript

I have a really simple CRUD app for managing music albums. Just two fields are tracked, title and artist.
In this example, the dropdown shows a list of albums, and if I fill out the form and click Save it will be added to the list.
In the second example, selecting an album will populate the form so it can be edited and updated.
My question is, is there a way to get both functionality in the same form? Sure I could create two identical forms and have them do slightly different things, but given they're operating on the same data, it would be nice if when a current_album is selected, it updates, and when "New album..." is selected, it creates.
The major roadblock seems to be value vs ng-model. I can set the value so it populates when I pick an item from the <select> OR I can set an ng-model="newAlbum", but not both to my knowledge.

You shouldn't be using the value attribute with ng-model. This is a very bad practice.
What I would suggest is to use ng-change on your list and keep a cloned object with the editing value.
$scope.onChange = function() {
if ($scope.currentAlbum) {
$scope.editing.title = $scope.currentAlbum.title;
$scope.editing.artist = $scope.currentAlbum.artist;
} else {
$scope.editing = {};
}
};
The all you need to do when saving is checking is it a new object or not:
$scope.addOrSaveAlbum = function() {
if ($scope.currentAlbum) {
$scope.currentAlbum.title = $scope.editing.title;
$scope.currentAlbum.artist = $scope.editing.artist;
} else {
$scope.albums.push({ title: $scope.editing.title, artist: $scope.editing.artist });
}
$scope.editing = {};
};
See http://jsfiddle.net/4Zeuk/12/
(thank you to Wawy to point out ng-change instead of $scope.$watch)

You can indeed get both functionality without the need of two different forms, but you can't use the same object in the scope in ng-model for both select and form fields.
But what you can do is have two different objects in the scope, one that contains the value of the selected item and the other will contain either a new instance of an album or a copy of the selected one. Then when you click the save/update button, based on the id of the object in the form you can decide if you need to save or modify the album collection.
Here is one way of doing what I've just explained:
<div ng-app="albumShelf">
<div ng-controller="MainCtrl">
<div style="float:left;">
<select ng-options="b.title for b in albums" ng-model="current_album" ng-change=updateForm()>
<option value="">Choose album...</option>
</select>
</div>
<div style="float:left;">
<form>
<input type="text" ng-model="newAlbum.title">
<br>
<input type="text" ng-model="newAlbum.artist">
<br>
<input type="submit" value="{{ current_album ? 'Update' : 'Save' }}" ng-click="modifyAlbumCollection()">
</form>
</div>
</div>
var albumShelf = angular.module('albumShelf', [/*'ngResource'*/])
.controller('MainCtrl', ['$scope', function($scope/*, albumFactory*/) {
//$scope.albums = albumFactory.query();
$scope.albums = [
{ id: 1, title: 'Disorganized Fun', artist: 'Ronald Jenkees' },
{ id: 2, title: 'Secondhand Rapture', artist: 'MS MR' }
];
$scope.modifyAlbumCollection = function() {
if ($scope.newAlbum.id !== null) {
//We are modifying an existing album
var i, found = false;
for (i = 0; i<$scope.albums.length && !found; i++) {
if ($scope.albums[i].id === $scope.newAlbum.id) {
$scope.albums[i] = angular.copy($scope.newAlbum);
found = true;
}
}
} else {
//We are adding a new album to the collection
$scope.newAlbum.id = $scope.albums.length;
$scope.albums.push(angular.copy($scope.newAlbum));
$scope.newAlbum = { id: null, title: '', artist: '' };
}
};
$scope.updateForm = function() {
if ($scope.current_album) {
//Copy the information from the selected album into the form.
$scope.newAlbum = angular.copy($scope.current_album);
} else {
//Clear previous album info.
$scope.newAlbum = { id: null, title: '', artist: '' };
}
};
}])
//.factory('albumFactory', function($resource) {
// return $resource('/albums/:albumId.json', { albumId: '#id' }
// );
//});
Here is the jsfiddle
In my opinion it's more clear if you use a ng-change in the select rather than a $watch on the ng-model value. Because what you want is update the form when a new value is selected from the dropdown rather than watching for changes on the object in the $scope.

Related

Allow users to filter data contained in the data collection in MeteorJS

Hello application must allow users filtration data contained in the data collection. The filter has to be supported by the checkboxes. The user selects (checkbox) it interesting data (name, address ...) and submit your selection. The table below displays the selected data from the data collection.
Does anyone have an idea how it should look like the code that retrieves data from the collection of data, taking into account filtration made by the user.
My idea was. Get the value of the checkbox and save them in the array and the later inclusion of data from the array in the code to retrieve data from the collection using the subroutine find ().
'change .cd-checkbox-1': function(event, target) {
var x = event.target.checked;
Session.set("statevalue", x);
var array = [];
if (Session.get("statevalue") === true) {
$( "[name='inputCheckboxPodmiot']:checked" ).each( function( index, element ) {
array.push( element.value );
});
};
var arrayLength = array.length;
for (var i = 0; i < arrayLength; i++) {
var abiRejestr = WkaRejestrAbi.find( {}, { fields: { array[i]: 1 } } );
}
}
One approach is to use Reactive Vars. Sessions are not recommended as they pollute the global namespace.
Example code :
In main.html
<template name="test">
<input type="checkbox" id="checkbox1" name="name" value="data">Check Me
<input type="checkbox" id="checkbox2" name="name" value="data">Check Me2
<input type="checkbox" id="checkbox3" name="name" value="data">Check Me2
{{showData}}
</template>
In Main.js
var check_status='';
//Reactive Var Initialization
Template.main.onCreated(function (){
check_status1=new ReactiveVar({});
check_status2=new ReactiveVar({});
check_status3=new ReactiveVar({});
});
Template.main.helpers({
showData : function(){
return Collection.find({$and : [{check_status1.get(),check_status2.get(),check_status3.get()}]}).fetch();
}
});
Template.main.events({
"change #checkbox1" : function(event) {
if($(event.currentTarget).is(":checked").val())
check_status1.set({field1: 'data1'});
else
check_status1.set({});
},
"change #checkbox2" : function(event) {
if($(event.currentTarget).is(":checked").val())
check_status2.set({field2: 'data2'});
else
check_status2.set({});
},
"change #checkbox3" :function(event) {
if($(event.currentTarget).is(":checked").val())
check_status3.set({field3: 'data2'});
else
check_status3.set({});
},
});
Explanation:
When we initialize the reactive var check_status we set the value equal to {}. In the helper, at the time of rendering, the same data is passed to the query Collection.find(check_status.get()) which is as good as show all data.
As soon as you click on the checkbox, the event described in the Template.main.events is triggered which sets the value of check_status to {field: data}. Since, this is a reactive var, the showData template is re run and this time the query is Collection.find({field: data}), so only fields, where field matched 'data' is returned.
You will need to add the reactive var package(meteor add reactive-var) before using these commands.
Alternatively, you can also use Tracker.autorun is you want to continue using session variables.

Replacing an Observable Array with AJAX

In our application we have a series of select filters that must be populated dynamically based on the context of the situation. On first load, the default options are inserted into the array via AJAX and it appears on the UI as expected. However, when the select list refreshes, the UI does not reflect the changes even though if you inspect the code the array appears to contain the new values.
I have written the same code for two filters but for some strange reason it only works in one of the situations, I have tried the following to resolve this to no avail:
Populating the array manually using arbitrary data
Forcing knockout to update with array.valueHasMutated()
Using the two different types of array clearing functions array.removeAll and array([])
Using push.apply and push
Saving the result to a variable and then assigning that to the array
Making the values inside the array observable
This first instance of the code works as expected when the options change:
success: function (data) {
self.filtersModel.values2.removeAll();
var serverData = $.map(data, function (value, i) {
return new SelectBoxOption(value.Description, value.Id);
});
serverData.forEach(function (value) {
self.filtersModel.values2.push(value);
});
}
This is the second function that does NOT work:
success: function (data) {
self.filtersModel.values.removeAll();
var other;
var serverData = $.map(data, function (value, i) {
// If the option is "other" save it as a variable and add to array later
if (value.Code === "OTHR") {
other = new SelectBoxOption(value.Description, value.Id);
self.filtersModel.othersValue(value);
}
else if (value.Code == "EQTY") {
var equity = new SelectBoxOption(value.Description, value.Id);
self.filtersModel.equityValue(value);
return equity;
}
else
return new SelectBoxOption(value.Description, value.Id);
});
serverData.forEach(function (value) {
self.filtersModel.values.push(value);
});
// Add "other" option to bottom of the array
if (other)
self.filtersModel.values.push(other);
}
Any help would be greatly appreciated.
UPDATE
HTML to populate the select list occurs like this:
<div class="form-group">
<label for="value">Value</label>
<select id="value" class="form-control" data-bind="value: selectedValue, options: values, optionsCaption: '-- ' + 'Select Value' + ' --', optionsValue: 'optionId', optionsText: 'optionName'"></select>
</div>
<div class="form-group">
<label for="value2">Value 2</label>
<select id="value2" class="form-control" data-bind="enable: $parent.valueIsAuthorisedAndvalueIsEquityOrOther, value: selectedValue2, options: values2, optionsCaption: '-- ' + 'Select Value 2' + ' --', optionsValue: 'optionId', optionsText: 'optionName'"></select>
</div>
Example data is returned in this format:
data = [
{optionId: 1, optionName: "Value 1"},
{optionId: 2, optionName: "Value 2"},
{optionId: 3, optionName: "Value 3"},
{optionId: 4, optionName: "Value 4"}
];
If I understand your question, the second code doesn't work because your are updating the self.filtersModel.othersValue and self.filtersModel.equityValue in every iteration.
I rewrote your code, hope it helps:
self.filtersModel.values.removeAll();
var normals = [];
var others = [];
var equities = [];
ko.utils.arrayForEach(data, function (d) {
if (d.Code === 'OTHR')
others.push(new SelectBoxOption(d.Description, d.Id);
else if (d.Code === 'EQTY')
equities.push(new SelectBoxOption(d.Description, d.Id);
else
normals.push(new SelectBoxOption(d.Description, d.Id);
});
self.filtersModel.values(normals.concat(equities).concat(others));
After much deliberation, I found that the issue was not related to the ajax function at all but rather a buggy method call I defined before the changing the select list:
self.clearFilters = function() {
self.filtersModel = new createFiltersModel();
}
function createFiltersModel() {
return {
values: ko.observableArray([])
}
};
This created a new instance of the filtersModel every time the select list changed, so any changes to the model were lost when the ajax call was made. Doh!

An array filtering issue with knockout and selectors

I have an array of texts that I display in a <select>.
The texts may have different version no, and I want to filter the <select> based upon the latest version only.
I guess there are more elegant ways to do it (suggestions welcome), but I´ve chosen to use 2 <select>s set to alternate visibility depending on the checkbox.
The code is a hack, but the result looks pretty good. Unfortunately there´s a bug.
I have two observables indicating the selected option in their respective arrays:
self.SelectedText = ko.observable();
self.SelectedUnique = ko.observable();
Both have subscriptions, but I cannot link them together in both subscription, so I have chosen one to be indipendant on the other like this:
self.SelectedUnique.subscribe(function (text) {
if (text) {
self.SelectedText(text);
}
});
However, the get out of sync.
Scenario 1: select text 1,2,3. [OK]
Scenario 2: select text 2; check "Latest versions only"
This causes no options ("Choose…") to be displayed. Not what I want.
It gets worse.
Scenario 3: uncheck; select text 3; Then check "Latest versions only" again.
Now the select option chosen is set to select option no 2 of the unfiltered.
There´s probably a simple issue. I just can´t make it work probably. Here´s the fiddle: Fiddle: http://jsfiddle.net/h5mt51gv/6/
All help and suggestions appreciated!
I have streamlined your approach:
the <select> binds to a computed list of options (visibleTextBatches)
this computed list depends on the state of the checkbox (latestOnly), effectively toggling between the full and the filtered list
the filtered list (latestTextBatches) is another computed that holds the latest version for each group
the <select> stores the actual selected TextBatch object in an observable (selectedTextBatch)
there is a subscription to visibleTextBatches that causes the latest selectable TextBatch to become the current one when the list is filtered. When the list is unfiltered, it does nothing.
function TextBatch(data) {
this.textbatchId = data.textbatchId;
this.parentId = data.parentId;
this.version = data.version;
this.title = ko.observable(data.title);
}
function ViewModel() {
var self = this;
// read up on the mapping plugin, too
self.textBatches = ko.observableArray([
new TextBatch({textbatchId: 1, parentId: 1, version: 1, title: "TB1.1"}),
new TextBatch({textbatchId: 2, parentId: 1, version: 2, title: "TB1.2"}),
new TextBatch({textbatchId: 3, parentId: 3, version: 1, title: "TB2.1"})
]);
self.selectedTextBatch = ko.observable();
self.latestOnly = ko.observable(false);
self.latestTextBatchGroups = ko.computed(function () {
var latest = {};
ko.utils.arrayForEach(self.textBatches(), function (batch) {
if (!latest.hasOwnProperty(batch.parentId) ||
batch.version > latest[batch.parentId].version
) latest[batch.parentId] = batch;
});
return latest;
});
self.latestTextBatches = ko.computed(function () {
return ko.utils.arrayFilter(self.textBatches(), function (batch) {
return batch === self.latestTextBatchGroups()[batch.parentId];
});
});
self.visibleTextBatches = ko.computed(function () {
return self.latestOnly() ? self.latestTextBatches() : self.textBatches();
});
self.visibleTextBatches.subscribe(function () {
var selectedBatch = self.selectedTextBatch();
if (selectedBatch && self.latestOnly()) {
self.selectedTextBatch(
self.latestTextBatchGroups()[selectedBatch.parentId]
);
}
});
}
ko.applyBindings(new ViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div>
<select data-bind="
options: visibleTextBatches,
optionsText: 'title',
optionsCaption: 'Select...',
value: selectedTextBatch
" />
</div>
<div>
<input type="checkbox" id="chkLatestOnly" data-bind="checked: latestOnly" />
<label for="chkLatestOnly">Latest only</label>
</div>
<hr />
<pre data-bind="text: ko.toJSON($root, null,2)"></pre>

Knock out Drop Down - Using Bool Values

I am working on a drop down control that is powered by Boolean values. I have a viewmodel that consists of the lookup dataset array (values and text attributes) and also a dataset that contains a Boolean value that needs to be aligned with that value chosen in the array. So data in the viewmodel represents an actual data item, while lookupdata represents essentially a mapping of value pairs. These two items really need to stay independent of eachother for the purposes of each data item containing only information relevant to it.
I have been able to get a sample working where I brought IsActive outside of the data object as it exists in viewmodel and placed it in viewmodel, however I can't get it to work while it is inside of viewmodel. I have created a JSFiddle that demonstrates functionally what I want the user to see, as well as one where I have the data where it belongs, but the drop down does not work as expected.
Active maps to true and Inactive maps to false. Switching between those should also affect the word true/false on the screen.
http://jsfiddle.net/rodearly/xF78A/11/
<div data-bind="with: data">
<select data-bind="options: $root.lookupData.status, optionsText: 'text', optionsValue: 'value', value: IsActive"></select>
<label>Value: </label>
<span data-bind="text: IsActive"></span>
</div>
<div>Alternative</div>
<div>
<select data-bind="options: lookupData.status, optionsText: 'text', optionsValue: 'value', value: IsActive"></select>
<label>Value: </label>
<span data-bind="text: IsActive"></span>
</div>
function Item(id, name) {
this.id = ko.observable(id);
this.name = ko.observable(name);
}
function getLookupData() {
var lookupData = {};
lookupData.status = [{
text: "Active",
value: true
}, {
text: "Inactive",
value: false
}];
return lookupData;
}
CreateImplantEditViewModel = function (data, lookupData) {
var vm = {};
vm.data = ko.observable(data);
vm.IsActive = ko.observable(false);
vm.lookupData = {};
vm.lookupData.status = ko.observableArray(lookupData.status);
return vm;
};
debugger;
var editImplantVm = CreateImplantEditViewModel({
IsActive: false
},
getLookupData());
ko.applyBindings(editImplantVm);
Thanks for any help,
~David
If I understand what you are attempting to do, your problem lies in the data that you are passing in to the 'CreateImplantEditViewModel' constructor. You need to create IsActive as an observable, otherwise it will never update:
var editImplantVm = CreateImplantEditViewModel({
IsActive: ko.observable(false)
},
getLookupData());
I've updated your jsfiddle, and both dropdowns now update their associated text binding when you change the selected option.
The problem here actually is that the Boolean values are being converted into string values. To get this to work, value needs to be either a string or a number.

How to assign array variable to select box dropdown options?

I have a form which is largely populated by checkboxes. The checkboxes each have an ID "value" that corresponds to an item within a javascript array. The array items hold some text that will populate a textarea.
I would like to include some dropdown boxes to clean up the site; however, I cannot seem to assign an array ID to the dropdown options? Can this be done in a selectbox option? Is there a workaround to simulate a selectbox without using the tab?
My html is basically:
<div>
<input type=checkbox id=array1 name=textArray></input>
<input type=checkbox id=array1 name=textArray></input>
<input type=checkbox id=array1 name=textArray></input>
...
<select><option 1><option 2>...</select>
</div>
<div>
<form>
<textarea id=outPut></textarea>
</form>
</div>
And my js is:
var textArray = {
array1: 'some text here',
array2: 'some more text',
array3: 'some other text',
...
array90: 'the last text'
};
// variable assigned to chosen item
var selectedInputs = document.getElementsByName("textArray");
for (var i = 0; i < selectedInputs.length; i++) {
selectedInputs[i].onchange = function() {
chosenItem = this;
printText();
};
}
// Script to add items to the Comments section text area
var mytextbox = document.getElementById('outPut');
var chosenItem = null;
function printText(){
if(chosenItem !== null){
mytextbox.value += textArray[chosenItem.id] + "";
// resets the radio box values after output is displayed
chosenItem.checked = false;
// resets these variables to the null state
chosenItem = null;
}
}
How can I associate an item in my js array with one of the selectbox choices?
I found it very difficult to understand what you're asking but I threw this together and hopefully it'll be helpful.
Important bit is
var selectNode = document.getElementById('select'); // <select id="select">
selectNode.onchange = function () {
if (selectNode.selectedIndex !== 0) {
chosenItem = selectNode.options[selectNode.selectedIndex];
selectNode.selectedIndex = 0;
printText();
}
}
and not to use the id attribute for what you're doing (I used data-i).
I'd also like to say that if you're cleaning up code this would be a good time to strongly reconsider how you're passing variables between functions; setting a value in the global namespace and relying on it in the next invocation is just asking for trouble (race conditions, conflicts with other bits of code, etc).
<option value="whatever">1</option> This has been part of HTML from the beginning.

Categories