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;
};
Related
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
}
})();
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
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.
If I have this code
var node = function(n) {
var name = n;
var children = [];
var finished = false;
var failed = false;
this.getName = function() {
return name
};
this.downloadData = function(obj) {
};
this.getChildren = function() {
return children;
};
this.setChildren = function(c) {
Array.prototype.push.apply(children, c);
};
this.isFinished = function() {
return finished;
};
this.setFinished = function() {
finished = true;
}
this.isFailed = function() {
return failed;
}
this.setFailed = function() {
failed = true;
}
};
How can I convert this into an object like:
var a = new node("a");
var j = JSON.stringify(a);
result
{"name":"a","children":[],"finished":false,"failed":false}
thanks
This could be done by implementing the toJSON function.
If an object being stringified has a property named toJSON whose value
is a function, then the toJSON() method customizes JSON
stringification behavior: instead of the object being serialized, the
value returned by the toJSON() method when called will be serialized.
- Mozilla
eg:
var node = function(n) {
var name = n;
var children = [];
var finished = false;
var failed = false;
this.toJson = function toJson() {
return {"name":name ... };
}
}
You need object properties instead of variables.
So, instead of declaring var name = n;, you would declare this.name = n;. Which would make it look something like
var node = function(n) {
this.name = n;
this.children = [];
this.finished = false;
this.failed = false;
///other functions here
}
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.