empty array in IIFE - javascript

I am trying to add a checkbox in header for all rows selection in jsgrid as shown here. It is working fine. But, instead putting all the code in a single page, I tried to put these functions inside IIFE as follows.
var jsgridCheckBox = (function() {
var selectedItems = [];
var selectItem = function(item) {
selectedItems.push(item);
...
};
var unselectItem = function(item) {
selectedItems = $.grep(selectedItems, function(i) {
return i !== item;
});
...
};
var selectAllCheckBox = function(item) {
selectedItems = [];
if (this.checked) {
...
}
};
return {
selectedItems: selectedItems,
selectItem: selectItem,
unselectItem: unselectItem,
selectAllCheckBox: selectAllCheckBox
}
})();
In JSGrid,
$(function() {
$("#jsGrid").jsGrid(
...
fields: [
itemTemplate: function(_, item) {
return $("<input>").attr("type", "checkbox").attr("class", "singleCheckbox")
.prop("checked", $.inArray(item, jsgridCheckBox.selectedItems) > -1)
.on("change", function() {
$(this).is(":checked") ? jsgridCheckBox.selectItem(item) : jsgridCheckBox.unselectItem(item);
});
}
...
]
});
});
And after selecting checkbox(s), when I try to call it as below
var dialog = $("#dialog-form").dialog({
buttons: {
"Reject": rejectRequest,
}
});
function rejectRequest() {
alert(jsgridCheckBox.selectedItems.length);
for (var i = 0; i < jsgridCheckBox.selectedItems.length; i++) {
alert(jsgridCheckBox.selectedItems[i].some_id);
...
}
...
}
it always returns 0. Is it that the jsgridCheckBox always returns a fresh instance? How can I maintain the state of the array in IIFE?

You have a private variable inside the closure there, selectedItems, and you also have a property on the object called selectedItems. That will cause confusion. The initial value of the property is just the empty array. When checkboxes are selected or unselected, selectedItems gets reassigned with your =; it's no longer referring to your original array. The reference will only remain if the array in question gets mutated (which isn't a great idea to rely on).
You can define a getter function that returns the current reference to the array:
var jsgridCheckBox = (function(){
var selectedItems = [];
var selectItem = function(item) {
selectedItems.push(item);
};
var unselectItem = function(item) {
selectedItems = $.grep(selectedItems, function(i) {
return i !== item;
});
};
var selectAllCheckBox = function(item) {
selectedItems = [];
if(this.checked) {
}
};
return{
getSelectedItems : () => selectedItems,
selectItem : selectItem,
unselectItem : unselectItem,
selectAllCheckBox :selectAllCheckBox
}
})();
Or you can always refer to the array as a property of the object, rather than as a standalone variable:
var jsgridCheckBox = (function(){
var selectItem = function(item) {
this.selectedItems.push(item);
};
var unselectItem = function(item) {
this.selectedItems = $.grep(this.selectedItems, function(i) {
return i !== item;
});
};
var selectAllCheckBox = function(item) {
this.selectedItems = [];
if(this.checked) {
}
};
return{
selectedItems : [],
selectItem : selectItem,
unselectItem : unselectItem,
selectAllCheckBox :selectAllCheckBox
}
})();

Related

How do I append to a mapped observable array in Knockout.js?

How do you add a row to an editable table in Knockout.js?
var data = {
"Lines": [
{"Entries": [{"Hours": 5.5},{"Hours": 2.50},{"Hours": 3.75}]},
{"Entries": [{"Hours": 5.1},{"Hours": 2.00},{"Hours": 4.75}]},
{"Entries": [{"Hours": 1.2},{"Hours": 3.00},{"Hours": 2.12}]}
]
}
var data1 = {"Entries": [{"Hours": 0},{"Hours": 0},{"Hours": 0}],Total:0};
The table displays self.List() which is an observableArray mapped to data.Lines with self.List(ko.mapping.fromJS(data.Lines)())
[{"Entries":[{"Hours":"33.5"},{"Hours":2.5},{"Hours":3.75}],"Total":39.75},{"Entries":[{"Hours":5.1},{"Hours":2},{"Hours":4.75}],"Total":11.85},{"Entries":[{"Hours":1.2},{"Hours":3},{"Hours":2.12}],"Total":6.32}]
When I click the addRow button I am thinking I need to recompute self.List(). I have tried from why-can-not-i-concat-data-to-observable-array-in-knockout
self.addRow =function(){
self.List(self.List().concat(data1))
self.applyTotals();
}
applyTotoals works fine if I don't add a row.
self.applyTotals = function(){
ko.utils.arrayForEach(self.List(), function(vm){
vm.Total = ko.computed(function(){
var s = 0;
ko.utils.arrayForEach(this.Entries(), function(entry){
var p = parseFloat(entry.Hours(), 10);
if (!isNaN(p)) {
s += p;
}
});
return s;
}, vm);
});
}
but I get uncaught TypeError:this.Entries is not a function and the new row won't compute totals. So I have tried
self.addRow =function(){
self.List = ko.computed(function(){
var orig = self.List();
var os= ko.toJS(orig);
os.push(data1)
console.log(JSON.stringify(os))
var oa = ko.observableArray([]);
return oa(ko.mapping.fromJS(os)());
})
}
How do I modify a mapped observableArrray?
Here is the fiddle: http://jsfiddle.net/mckennatim/jngesuf2/
well #mcktimo you are not effectively using mapping plugin . you can make use of 2nd paramter Mapper in fromJS function and build you viewModel effectively .
viewModel:
function model(data) {
var self = this;
self.Entries = ko.observableArray();
self.Total = ko.computed(function () {
var sum = 0;
ko.utils.arrayForEach(self.Entries(), function (entry) {
var value = parseFloat(entry.Hours(), 10);
if (!isNaN(value)) {
sum += value;
}
});
return sum;
});
ko.mapping.fromJS(data, {}, self);
}
var mapping = { //everything goes through this point
create: function (options) {
return new model(options.data);
}
}
function ViewModel() {
var self = this
self.List = ko.observableArray([])
self.LoadData = function (data) {
ko.mapping.fromJS(data.Lines, mapping, self.List)
}
self.LoadData(data);
self.addRow = function () {
self.List.push(ko.mapping.fromJS(data1, mapping));
}
}
ko.applyBindings(new ViewModel(), document.getElementById('ko'))
working sample here
I suggest to take a deeper dive into the mapping documentation

Angular service function reset not working as expected

I have the following service:
app.service('ItemService', function() {
my_items = [];
var removeItem = function(newObj) {
var toRemove = newObj;
var indexOf = my_items.indexOf(toRemove);
my_items.splice(indexOf, 1);
};
var resetItems = function(){
my_items = [];
};
var getItems = function(){
return my_items;
};
return {
removeItem: removeItem,
resetItems: resetItems,
getItems: getItems
};
});
and in my controller i have:
$scope.my_items = ItemService.getItems();
$scope.myCheckPush = function(item) {
ItemService.addItem(item);
}
$scope.myCheckRemove = function(item) {
ItemService.removeItem(item);
};
$scope.reset = function () {
ItemService.resetItems();
};
Here my remove function works well. But the reset function is not working as expected. Any ideas what i might be doing wrong?
Both my_items and $scope.my_items point to the same array. When you do my_items = [], you just remove the reference from my items, but since $scope.my_items still holds the reference the array is not deleted.
To delete the array use my_items = 0, which will actually delete the array's contents effecting $scope.my_items as well.
var resetItems = function(){
my_items.length = 0;
};

JavaScript: Prevent Array.push()

I have a sealed object with an array member on which I want to prevent direct pushes.
var myModule = (function () {
"use strict";
var a = (function () {
var _b = {},
_c = _c = "",
_d = [];
Object.defineProperty(_b, "c", {
get: function () { return _c; }
});
Object.defineProperty(_b, "d", {
get { return _d; }
});
_b.addD = function (newD) {
_d.push(newD);
};
Object.seal(_b);
return _b;
}());
var _something = { B: _b };
return {
Something: _something,
AddD: _b.addD
};
}());
myModule.Something.c = "blah"; // doesn't update = WIN!!
myModule.AddD({}); // pushed = WIN!
myModule.Something.d.push({}); // pushed = sadness
How can I prevent the push?
UPDATE:
Thanks for all the thoughts. I eventually need the JSON to send to the server. It looks like I might need to use an object for the array then figure out a way to generate and return the JSON needed, or change _something to use .slice(). Will play and report.
you could override the push method:
var _d = [];
_d.__proto__.push = function() { return this.length; }
and when you need to use it in your module, call Array.prototype.push:
_b.addD = function (newD) {
Array.prototype.push.call(_d, newD);
};
I haven't done any performance tests on this, but this certainly helps to protect your array.
(function(undefined) {
var protectedArrays = [];
protectArray = function protectArray(arr) {
protectedArrays.push(arr);
return getPrivateUpdater(arr);
}
var isProtected = function(arr) {
return protectedArrays.indexOf(arr)>-1;
}
var getPrivateUpdater = function(arr) {
var ret = {};
Object.keys(funcBackups).forEach(function(funcName) {
ret[funcName] = funcBackups[funcName].bind(arr);
});
return ret;
}
var returnsNewArray = ['Array.prototype.splice'];
var returnsOriginalArray = ['Array.prototype.fill','Array.prototype.reverse','Array.prototype.copyWithin','Array.prototype.sort'];
var returnsLength = ['Array.prototype.push','Array.prototype.unshift'];
var returnsValue = ['Array.prototype.shift','Array.prototype.pop'];
var funcBackups = {};
overwriteFuncs(returnsNewArray, function() { return []; });
overwriteFuncs(returnsOriginalArray, function() { return this; });
overwriteFuncs(returnsLength, function() { return this.length; });
overwriteFuncs(returnsValue, function() { return undefined; });
function overwriteFuncs(funcs, ret) {
for(var i=0,c=funcs.length;i<c;i++)
{
var func = funcs[i];
var funcParts = func.split('.');
var obj = window;
for(var j=0,l=funcParts.length;j<l;j++)
{
(function() {
var part = funcParts[j];
if(j!=l-1) obj = obj[part];
else if(typeof obj[part] === "function")
{
var funcBk = obj[part];
funcBackups[funcBk.name] = funcBk;
obj[part] = renameFunction(funcBk.name, function() {
if(isProtected(this)) return ret.apply(this, arguments);
else return funcBk.apply(this,arguments);
});
}
})();
}
}
}
function renameFunction(name, fn) {
return (new Function("return function (call) { return function " + name +
" () { return call(this, arguments) }; };")())(Function.apply.bind(fn));
};
})();
You would use it like so:
var myArr = [];
var myArrInterface = protectArray(myArr);
myArr.push(5); //Doesn't work, but returns length as expected
myArrInterface.push(5); //Works as normal
This way, you can internally keep a copy of the interface that isn't made global to allow your helper funcs to modify the array as normal, but any attempt to use .push .splice etc will fail, either directly, or using the .bind(myArr,arg) method.
It's still not completely watertight, but a pretty good protector. You could potentially use the Object.defineProperty method to generate protected properties for the first 900 indexes, but I'm not sure of the implications of this. There is also the method Object.preventExtensions() but I'm unaware of a way to undo this effect when you need to change it yourself
Thank you, dandavis!
I used the slice method:
var myModule = (function () {
"use strict";
var a = (function () {
var _b = {},
_c = _c = "",
_d = [];
Object.defineProperty(_b, "c", {
get: function () { return _c; }
});
Object.defineProperty(_b, "d", {
get { return _d.slice(); } // UPDATED
});
_b.updateC = function (newValue) {
_c = newValue;
};
_b.addD = function (newD) {
_d.push(newD);
};
Object.seal(_b);
return _b;
}());
var _something = { B: _b };
return {
Something: _something,
AddD: _b.addD
};
}());
myModule.Something.c = "blah"; // doesn't update = WIN!!
myModule.AddD({}); // pushed = WIN!
myModule.Something.d.push({}); // no more update = happiness
This allows me to protect from direct push calls enforcing some logic.

Knockout - How to reset Dynamic observable array with new value

I can't reset observable array with new value am using some lazy loading Technic.
I can clear but can't reset, but it not allowing me to add new dynamic value.
fiddle
http://jsfiddle.net/kspxa8as/
js
var i = 1;
optionsProvider = function(self) {
var self = self || {};
self.options = {};
self.get = function(name, initialValue) {
if (!self.options[name]) {
console.log("Called - " + name);
self.options[name] = ko.observableArray([initialValue]);
var requestHeader = '';
setTimeout(function() {
var aa = [{name: "plant 1" + i, selected: true}, {name: "palnt 2" + i, selected: false}];
self.options[name](aa);
i++;
}, 2000);
}
return self.options[name];
};
return self;
};
ViewModel = function() {
var self = this;
var k = 1;
var ob = new optionsProvider(self);
self.PlantSelected = ob.get("name" + k, '');
self.fillNewSelect = function() {
self.PlantSelected.removeAll();
self.PlantSelected().push(ob.get("name" + k, ''));
k++;
};
};
ko.applyBindings(new ViewModel());
HTML
<select class="n_imported_country"
data-bind="options: PlantSelected,
optionsText :'name'
"
>
</select>
<div data-bind="click: function(){
$root.fillNewSelect();
}">click to fill new select value</div>
I am a newbie to knockout, great welcome your answers.
I recommend the use of a promise library to handle the asynchronous Ajax load of new items. I've used jQuery's implementation in the sample below. Notice how optionsProvider no longer requires any dependency on the viewmodel.
var optionsProvider = function (name, initialValue) {
return function () {
return $.get("/target/url", {parameter: "value"})
.fail(function () {
console.log("request to get new items failed", arguments);
});
};
};
var ViewModel = function () {
var self = this,
k = 1,
ob = optionsProvider("name" + k, '');
self.PlantSelected = ko.observableArray([]);
self.fillNewSelect = function () {
ob().then(function (newData) {
var p = self.PlantSelected;
p.removeAll();
p.push.apply(p, newData);
});
};
// init
self.fillNewSelect();
};
ko.applyBindings(new ViewModel());
The second change to mention is the way to push new objects into an array. .push() supports an argument list:
arr.push('a', 'b', 'c')
If you have an array of items you want to push (for example a JSON result), you would use .apply(), otherwise you would push the array itself as the first item:
arr.push.apply(arr, ['a', 'b', 'c']);
Observable arrays in knockout support the same usage.
Compare: http://jsfiddle.net/kspxa8as/6/
Try something like this the trick here is passing the instance of the observableArray to the function and get our job done
Viewmodel:
var ViewModel = function() {
var self = this;
var k = 1;
var ob = new optionsProvider(self);
self.PlantSelected = ko.observableArray(); //Declare it as observableArray
ob.get("name" + k, '',self.PlantSelected) // call the function passing observableArray instance
self.fillNewSelect = function()
{
self.PlantSelected.removeAll();
ob.get("name", ''+ k,self.PlantSelected)
k++;
};
};
ko.applyBindings(new ViewModel());
function :
var i = 1;
optionsProvider = function(self) {
var self = self || {};
self.options = {};
self.get = function(name, initialValue,instance) { //pass observableArray instance here
if (!self.options[name] || self.options[name]()) {
var requestHeader = '';
setTimeout(function() {
var aa = [{name: "plant 1" + i, selected: true},
{name: "palnt 2" + i, selected: false}];
instance(aa); // assign data to instance
},2000);
i++;
}
return true;
};
return self;
};
working sample here

Javascript - private array

Im struggling with something quite simply in javascript. I want to have an array which is modifiable only through methods of an object. Consider the following example:
var Cart = function() {
this.items = [];
}
Cart.prototype.getItems = function() {
return this.items;
}
Cart.prototype.addItem = function(item) {
this.items.push(item);
}
module.exports = Cart;
I want to be able to add new items through the addItem method and to retrieve the items through the getItems method. I dont want to be able to just do Cart.items.push(item) for example.
How can i achieve this?
With ES6 WeakMap you can do it like this:
var items = new WeakMap();
var Cart = function() {
items[this] = [];
};
Cart.prototype.getItems = function() {
return items[this];
};
Cart.prototype.addItem = function(item) {
items[this].push(item);
};
module.exports = Cart;
In javascript you don't have private properties. Here are some options:
1 - call property _items. That's a well known convention for naming private properties;
2 - use closure:
var Cart = function() {
var items = [];
this.getItems = function() {
return items;
};
this.addItem = function(item) {
items.push(item);
};
}
3 - use symbols. They are not truly private, but hard to discover:
var items = Symbol();
var Cart = function() {
this[items] = [];
}
Cart.prototype.getItems = function() {
return this[items];
}
Cart.prototype.addItem = function(item) {
this[items].push(item);
}
4 - use private-symbol module instead. This will work only in node.js/io.js (uses v8 C++ API), but gives you truly private symbols
5 - use WeakMap
var items = new WeakMap();
var Cart = function() {
items.set(this, []);
}
Cart.prototype.getItems = function() {
return items.get(this);
}
Cart.prototype.addItem = function(item) {
items.get(this).push(item);
}
Will this meet your requirements?
var Cart = function() {
var items = [];
this.getItems = function() {
return items;
};
this.addItem = function(item) {
items.push(item);
};
}
You can achieve that functionality by leveraging the scope of your constructor.
Instead of defining the array as a public property, you define it as a variable:
var Cart = function() {
var items = [];
this.getItems = function() {
return items;
}
this.addItem = function(item) {
items.push(item);
}
this.addAndRetrieve = function(item) {
items.push(item);
return items;
}
}
module.exports = Cart;
Then, you can access it through the publicly exposed methods getItems and addItem
var x = new Cart(),
y = new Cart();
x.addItem(1);
y.addItem(2);
x.getItems(); // [1]
y.getItems(); // [2]
EDIT1: If these are the only 2 methods that you want to chain on your instance, you can simply combine them in a new function and achieve the combined functionality.

Categories