I want to convert a solution of <select /> box chaining I've already built to use KnockoutJS. Here's how it works now:
I have a database that is full of products that have attributes and those have values which in turn have a dependency on another selected value.
product > attributes > values > dependency
bench > length > 42" > (height == 16")
In my database we also store what values are dependent on other values. e.g. length can only be 42" if the height is 16" or something like that.
This comes from the server to a JSON object on the client that contains all of the attributes, values and dependencies for the product in a tree like form.
var product =
{
attributes:
[
values:
[
value:
{
dependencies: [{dependencyOp}]
}
]
]
};
I'll loop through each value and its dependency for the entire object and build an expression like "16 == 14 && 4 == 4" which would evaluate to false of course (14 being the selected value from another attribute). in that expression the && would be the dependencyOp in the dependencies array.
Now in my attempt I used KnockoutJS mapping plugin to get the object to be my view model but my problem is when I make a dependentObservable that needs to know what its dependant on. So now I would have to loop through every single array/object in my product variable?
If I understood your question, you're trying to get data from your server, and use it determine if the user's input is valid. If that's the case, put your data structure into a field in your viewModel, and make your dependentObservable dependent on that field.
function ViewModel() {
this.data = ko.observable();
this.input = ko.observable();
this.isValid = ko.dependentObservable(function() {
// evaluate input() against data() to determine it is valid; return a boolean
return ...;
}, this);
this.loadData = function() {
$.ajax('/data/get', {
success: function(dataFromServer) {
this.data(dataFromServer);
});
}
}
var vm = new ViewModel();
ko.applyBindings(vm);
vm.loadData();
now you can refer to this dependentObservable in a data-bind attribute like this
<input type="text" data-bind="value: input, valueupdate='afterkeydown'" />
<div data-bind="visible: isValid">shown only when data is valid...</div>
Related
I am facing a problem with the filter function in Angularjs, here I used a common function ng-change for multiple a dropdown. But when I select All(default options) I am getting an empty array of filtered items.
Here is my code
Javascript:
function customFilters() {
vm.filteredItems = $filter('filter')(vm.claimsResData, {
status: vm.status,
member: vm.member,
treatment: vm.treatment
});
}
Html:
<select ng-model="vm.member" class="form-control"
ng-options="names for names in vm.memberNames"
ng-change="vm.customFilters()">
<option value="">All Members</option>
</select>
Any help would be appreciated.
Thanks.
Update your function as below, because when you want to exclude a key from filter you've to pass undefined to its value, but in your code you are passing blank string i.e., ""
function customFilters() {
vm.filteredItems = $filter('filter')(vm.claimsResData, {
status: vm.status,
member: vm.member || undefined,
treatment: vm.treatment
});
}
Only add the selection to the pattern object if the selection is truthy:
function customFilters() {
var patternObj = {}
vm.status && patternObj.status = vm.status;
vm.member && patternObj.member = vm.member;
vm.treatment && patternObj.treatment = vm.treatment;
vm.filteredItems = $filter('filter')(vm.claimsResData, patternObj);
}
A pattern object is used to filter specific properties on objects contained by the array. When that property exists on the pattern object, the objects in the array must have that property.
For more information, see
AngularJS filter Filter API Reference
I have an input field that I would like to either be blank or populate depending on the condition
Condition A: new student, blank field
Condition B: existing student, populated field
This data is coming from the data() function or a computed: property. For example:
data () {
return {
studentName: '', // empty student name property (/new student route)
studentPersonality: ''
}
},
computed: {
...mapGetters({
getStudent // existing student object (/edit student route)
})
}
My input field should either be blank if we are arriving from the /new student route, or populate the field with the existing student info, if we're coming from the /edit route.
I can populate the input field by assigning getStudent.name to v-model as shown below.
<input type="text" v-model="getStudent.name">
...and of course clear the field by instead assigning studentName to v-model
<input ... v-model="studentName">
Challenge: How can I use getStudent.name IF it exists, but fall back on the blank studentName data() property if getStudent.name does NOT exist? I have tried:
<input ... v-model="getStudent.name || studentName">
...which seemed to work, but apparently invalid and caused console errors
'v-model' directives require the attribute value which is valid as LHS
What am I doing wrong?
There's really no need to have the input field register to different properties in your vue component.
If you want to have a computed property that is also settable, you can define it using a set & get method.
computed: {
student_name: {
get: function() {
return this.$store.getters.get_student.name
},
set: function(val) {
this.$store.commit('set_student_name');
}
}
}
One other way is to separate the value from the input change handler in the input element itself, in this case you would use the getter as you've set it
<input type="text" :value="getStudent.name" #input="update_name($event.target.value)">
And lastly, if you need to really use two different properties you can set them on a created/activated hook (and answering your original question):
created: function() {
this.studentName = this.getStudent
},
activated: function() {
this.studentName = this.getStudent
}
You'll always need to delegate the update to the store though so I would either go with the get/set computed property, or the value/update separation
I have a simple ng-repeat like that:
<input type="text" ng-model="vm.filter">
<div ng-repeat="tenant in vm.tenants | filter : vm.filter : vm.contains">
</div>
Now I want to filter tenants based on the value of the filter, e.g. to find if a name of a tenant contains the filter expression like that
function contains(actual, expected) {
return actual.name.indexOf(expected) >= 0;
}
What I do not understand is why I get a tenant.name in the contains function instead of the tenant itself. I know for a simple case I can do something like filter | {name: vm.filter} | vm.contains, but what if I want to do filtering based on multiple properties (e.g. name, phone, etc.)
What I do not understand is why I get a tenant.name in the contains
function instead of the tenant itself.
What's happening is that angular is providing the comparator all levels of the object in an attempt to do a deep filtering i.e. it attempts to match your filter to all hierarchy levels in the object. If you console.log all the values, you'll see that you get the full object too (after all the properties).
One way to make this work would be something like
...
<div ng-repeat="tenant in vm.tenants | filter : vm.contains(vm.filter)">
...
and then
...
contains: function(filter) {
return function(tenant) {
return tenant.name.indexOf(filter) !== -1;
}
}
...
Fiddle - https://jsfiddle.net/81384sd7/
You can use an object to store the filters you want to apply. For example:
$scope.filterBy = {
name: "Bob",
phone: "07"
};
Then configure fields to edit the filters:
<input type="text" ng-model="filterBy.name" />
<input type="text" ng-model="filterBy.phone" />
Then just filter by this object.
<div ng-repeat="tenant in vm.tenants | filter : filterBy">
Take a look at my previous answer which is very similar except also allows selections to be made before a filter being applied manually with a button:
Applying a angularjs filter after "Apply" button click
I hope this helps.
You'll want to test for the presence of both those conditions and return the value if both are present. Something like this:
function contains(actual, expected) {
if (actual.name.indexOf(expected) >= 0 && actual.phone.indexOf(expected) >= 0) {
return actual;
}
}
Here is a fiddle : http://jsfiddle.net/BNXTm/1/
As you can see, even though the new consultant has the Commercial role, the select displays Consultant instead of Commercial. How can I make the select elements display the name of the consultant's role?
value binding compare objects references to match selected value.
The Role object in the list and the selected object does not share the same reference
http://jsfiddle.net/BNXTm/4/
var c1 = new Consultant("Foo BAR", ko.utils.arrayFirst(contractViewModel.availableRoles, function(item) {
return item.tag === "Co";
}));
What you can do is create an computed which works with a plain value instead of objects:
self.role = ko.observable(role);
self.role.forSelect = ko.computed({
read: function() {
return self.role().tag;
},
write: function(newValue) {
self.role(contractViewModel.getRole(newValue));
}
});
This way role will always be one of the objects in availableRoles.
See http://jsfiddle.net/eCkL9/
I'm using typeahead angular bootstrap which is a very neat directive, but I'm encountering a difficulty to get the select value when using it with an array of objects (needed for a custom template ). I can't get the typeahead selection value (it's displayed correctly but passed as [object object] to the target controller.
this is the form:
<form ng-submit="startSearch(search.term , false, true)" novalidate>
<input typeahead-editable="true" typeahead="item as label(item) for item in startSearch($viewValue, true)| limitTo:10"
required type="text" class="searchInput" ng-model="search.term"
typeahead-template-url="views/search/typeahead.html" /> <!--| filter:$viewValue typeahead-on-select="goState(selectState(select), label(select)"-->
<button class="searchBT" ng-submit="startSearch(search.term , false, true)"><i class="icon-search"></i></button>
and the (relevant) controller:
$scope.search = {};
$scope.startSearch = function(term, suggest, submitted){
var deferred = $q.defer();
if (submitted) {
console.log($scope.search)
$state.go('search.home',{'term': $scope.search.term }); //term gets [object object] instead of the displayed name
} else {
searchService.doSearch(term, suggest).then(function(data){
"use strict";
// console.log('data',data)
if (suggest){
deferred.resolve(data)
}
}, function(err){
"use strict";
$log.debug('err',err);
})
}
return deferred.promise;
}
};
$scope.label = function(item){
"use strict";
//select label
if (!item) {
return;
}
return item.symbol || (item.first_name +" " + item.last_name)
}
to summarize my problem:
I get a displayed value (and I get a correct one) from the typeahead select list it seems not to update the model (search.term) correctly and I get some [object object] result there.
If I manually type a value in the input field and submit I do get a correct value (and state transition etc..)
to make things a little more complicated, the list contains different kind of objects so I to handle some logic about the object with a function/filter and can't just grab a field
Thanks for the help!
I think this may be that you are missing a property name off of the object.
I have written a plunk based on your code here. I have just stripped back the service calls to be a hard-coded array.
Pay particular attention to the object that is returned has a "Value" and "Text" property.
var results = [];
angular.forEach([{Val: 1, Text: "AngularJS"}, {Val: 2, Text: "EmberJS"}, {Val: 3, Text: "KnockoutJS"}], function(item){
if (item.Text.toLowerCase().indexOf(term.toLowerCase()) > -1) {
results.push(item);
}
});
return results;
And also look in the markup at the way the "typeahead" attribute is populated using the actual text property of the object:
typeahead="item.Text for item in startSearch($viewValue)| limitTo:10"