📜 ⬆️ ⬇️

Linq-like syntax for knockout

A year has passed since our team develops a web portal using the MVVM pattern and the Knockout framework in particular. Little by little, experience was accumulated, various solutions appeared, good and bad practices, and now, so to speak, overdue. The linq.js library already exists for the linq syntax in javascript, and for a long time we thought whether to drag it into our project. And even examples of use, coupled with knockout on the Internet is .
The idea that befell me was to create a computed encapsulation inside Linq-methods.
For comparison, the code from fiddle:
this.filteredItems = ko.computed(function() { var term = this.searchTerm(); return this.items.where(function(item) { return item.name.indexOf(term) > -1; }); }, this); 

and code that I would like to write instead:
  this.filteredItems = this.items .Where(function(item) { return item.name.indexOf(this.searchTerm()) > -1; }); 


After computed the above encapsulation, it turned out that the linq.js library is not really needed. Enough tools built into the knockout. Moreover, they need to be written only once, and there will be no difference from the outside, even if it is the most direct and simple for loop.

So, first prepare the object with the methods:
 var methods = { First: function(predicate) { return ko.computed(function() { return ko.utils.arrayFirst(this(), predicate); }, this, { deferEvaluation: true }); }, Select: function(func) { return ko.computed(function() { return ko.utils.arrayMap(this(), function(item) { return ko.utils.unwrapObservable(func(item)); }); }, this, { deferEvaluation: true }); }, SelectMany: function(func) { return ko.computed(function() { var result = []; ko.utils.arrayForEach(this(), function(item) { result = result.concat(ko.utils.unwrapObservable(func(item))); }); return result; }, this, { deferEvaluation: true }); }, Where: function(predicate) { return ko.computed(function() { return ko.utils.arrayFilter(this(), predicate); }, this, { deferEvaluation: true }); }, Distinct: function(func) { if (!func) { return this.DistinctValue(); } return ko.computed(function() { var obj = {}; return ko.utils.arrayFilter(this(), function(item) { var val = ko.utils.unwrapObservable(func(item)); return obj[val] ? false : (obj[val] = true); }); }, this, { deferEvaluation: true }); }, DistinctValue: function() { return ko.computed(function() { var obj = {}; return ko.utils.arrayFilter(this(), function(val) { return obj[val] ? false : (obj[val] = true); }); }, this, { deferEvaluation: true }); }, Sum: function(func) { return func ? this.Select(func).Sum() : this.SumValue(); }, SumValue: function() { return ko.computed(function() { var result = 0; ko.utils.arrayForEach(this(), function(item) { result = result + (+item); }); return result; }, this, { deferEvaluation: true }); }, StringJoin: function(joinString) { joinString = joinString || ', '; return ko.computed(function() { return this().join(joinString); }, this, { deferEvaluation: true }); }, }; 

The second action we hang the methods on obsrvableArray and computed:
  for (var i in methods) { ko.observableArray.fn[i] = methods[i]; ko.computed.fn[i] = methods[i]; } 

The dish is ready to use. Examples:
  self.DistinctEntities = policy.Coverages .SelectMany(function(item) { return item.Entities; }) .Distinct(function(item) { return item.Name; }); self.EmployeeCount = policy.CoveredTotalCurrentYear .Sum(function (item) { return item.Quantity; }); self.LineOfCoverageColumnName = policy.Coverages .Select(function (item) { return item.LineOfCoverage.ShortDisplayName; }) .StringJoin(); 


And for a snack, the Map method is similar to the Select method, but for complex / expensive operations, in particular, when for each data model in the input array, you need to create a view model in the output. When adding an element to the input array, the Select operation will recall the “lambda” for all the elements of the array, while the Map operation will only do this for the newly added element:
 Map: function (converter) { var oldValues = []; var oldResults = []; return ko.computed(function() { var values = this().slice(); var results = []; ko.utils.arrayForEach(values, function(item) { var index = oldValues.indexOf(item); results.push(index > -1 ? oldResults[index] : converter(item)); }); oldValues = values; oldResults = results; return results; }, this, { deferEvaluation: true }); }, 

Using:
 self.Coverages = policy.Coverages.Map(function(coverage) { return new coverageViewModel(coverage); }); 

')
PS The list of methods does not yet cover the entire LinQ set, but it is easy to expand.

Source: https://habr.com/ru/post/221763/


All Articles