Related
I have an array which is a state of the React component. This array is a checklist.
var units1 = this.state.units;
when I update units1, this.state.units changes without the this.setState({ units: units1 })
I use this.setState({ a: 2 }); just to see if the array was updated without this.setState({ units: units2 });
this.state.units gets its value from props so if the state changes the props also changes.
handleItemChange(e) {
var units1 = this.state.units.slice();
var unit_name = parseInt(e.target.attributes.getNamedItem('data-unit_name').value);
var new_unit;
if (!e.target.checked && this.state.units && this.state.units.length > 0) {
this.state.units.map((unit) => {
if (unit_name == unit.codpacunidad) {
if (unit.topics && unit.topics.length > 0) {
unit.topics.map((topic) => {
if (topic.codpacunidadtema == e.target.name) {
new_unit = unit;
var index = units1.indexOf(unit);
//units1.splice(index, 1);
units1 = update(units1, {$splice: [[index, 1]]})
var topics1 = unit.topics.slice();
index = topics1.indexOf(topic);
//topics1.splice(index, 1);
topics1 = update(topics1, {$splice: [[index, 1]]})
new_unit.topics = topics1;
}
});
}
}
});
} else {
var found_unit = false;
var name = parseInt(e.target.name);
var order = parseInt(e.target.attributes.getNamedItem('data-order').value);
var unit_order = parseInt(e.target.attributes.getNamedItem('data-unit_order').value);
if (this.state.units && this.state.units.length > 0) {
this.state.units.map((unit) => {
if (unit.codpacunidad == unit_name) {
found_unit = true;
new_unit = unit;
var index = units1.indexOf(unit);
units1.splice(index, 1);
var new_topic = {
codpacunidadtema: name,
orden: order
};
var topics2 = new_unit.topics;
new_unit.topics = update(topics2, { $push: [new_topic]});
}
});
}
if (found_unit == false) {
new_unit = {
codpacunidad: unit_name,
orden: unit_order,
topics: [{codpacunidadtema: name, orden: order }]
};
}
}
// var units2 = update(units1, { $push: [new_unit]});
// this.setState({ units: units2.sort(function(a, b) {
// return parseInt(a.orden) - parseInt(b.orden);
// })
// });
this.setState({ a: 2 }); //just to test if the array gets updated without this.setStaet({ units: units2 })
}
Anybody knows why this is happening?
As #Alexander van Oostenrijk said to make deep copy of array.
Because array are passed by reference which mean memory address of the array is passed not the value of array.
var units1 = this.state.units.slice();
Now units1 has the reference address of that array any change made to units1 or this.state.units.slice() will change value of both.As basically they are using address and if one change the value at address then both will read changed value.Hope you understand
To create deep copy you can create new object like
var units1 = Object.assign([],this.state.units)
This will create new object with data of this.state.units
extras I think you do not need .slice().
I wrote a little program in angular using ui-select. And I want to write a filter that do an OR search in different fields (that can be nested fields in a json). In the github of ui-select I found this filter that can do a similar things (but only with simple fields) :
/**
* AngularJS default filter with the following expression:
* "person in people | filter: {name: $select.search, age: $select.search}"
* performs an AND between 'name: $select.search' and 'age: $select.search'.
* We want to perform an OR.
*/
app.filter('propsFilter', function() {
return function(items, props) {
var out = [];
if (angular.isArray(items)) {
var keys = Object.keys(props);
items.forEach(function(item) {
var itemMatches = false;
for (var i = 0; i < keys.length; i++) {
var prop = keys[i];
var text = props[prop].toLowerCase();
if (item[prop].toString().toLowerCase().indexOf(text) !== -1) {
itemMatches = true;
break;
}
}
if (itemMatches) {
out.push(item);
}
});
} else {
// Let the output be the input untouched
out = items;
}
return out;
};
});
My json object where I want to apply this filter has this structure :
$scope.contracts = [{
name: "contract1.00",
value: 10,
id :{
id : 8000,
code : 2
},
policy : {
info : {
name : "test1",
country : "test"
}
}
}
//other elements....
The problem is that this 'propsFilter' only works with simple fields. So, if I write this :
propsFilter: {name: $select.search, value : $select.search}
It will work correcly and do an OR search in these two fields (name OR values). But in my example, I want to do the OR search with two others fields : id.id and policy.info.name.
So, what I want to do is to use a line like this :
propsFilter: {name: $select.search, value : $select.search, id.id : $select.search, policy.info.name : $select.search }
Finally, here is my plunker : http://plnkr.co/edit/ej2r7XqeTPOC5d1NDXJn?p=preview
How can I do that in the same search filter??
I've updated your plunker. First of all, break from the loop caused the filter to work only on first property name, I've also added a condition in the if item[prop] && so your code doesn't throw an error when the property does not exist on the item
http://plnkr.co/edit/CwNANzodvjnuMCNyJYtA?p=preview
app.filter('propsFilter', function($parse) {
return function(items, props) {
var out = [];
if (angular.isArray(items)) {
var keys = Object.keys(props);
items.forEach(function(item) {
var itemMatches = false;
for (var i = 0; i < keys.length; i++) {
var prop = $parse(keys[i])(item);
var text = props[keys[i]].toLowerCase();
if (prop && prop.toString().toLowerCase().indexOf(text) !== -1) {
itemMatches = true;
}
}
if (itemMatches) {
out.push(item);
}
});
} else {
// Let the output be the input untouched
out = items;
}
return out;
};
});
I am using the ag-grid for angular1, (and loving it), and I want my users to be able to reorgenize columns, change sortings, and everything, and that it will stay after a refresh.
It should not be very hard, except that the columns are circular (contains pointers to themselves), and thus I cannot parse them.
Code:
var columnDefsKey = "columnDefs["+$rootScope.page+"]";
var savedColumns = localStorage.getItem(columnDefsKey);
function saveColumnsState() {
var currentCol = vm.gridOptions.columnApi.getAllColumns();
if (!angular.equals(currentCol, savedColumns))
try {
localStorage.setItem(columnDefsKey, JSON.stringify(currentCol));
} catch (ex) {
log(ex);
log(currentCol);
}
}
And:
onColumnEverythingChanged: saveColumnsState,
onColumnVisible: saveColumnsState,
onColumnPinned: saveColumnsState,
onColumnResized: saveColumnsState,
onColumnRowGroupChanged: saveColumnsState,
onColumnValueChanged: saveColumnsState,
onColumnMoved: saveColumnsState,
onColumnGroupOpened: saveColumnsState,
It fails on the "try" every time:
TypeError: Converting circular structure to JSON(…) [Column, Column, Column, Column, Column, Column, Column, Column, Column, Column]
How can I do that? (save columns for later use)
If I manage to do that, I will be able to create several views without coding.
you can get the better understanding of the issue from below link
Chrome sendrequest error: TypeError: Converting circular structure to JSON
Also check below reference
https://github.com/isaacs/json-stringify-safe
The way to achieve this was to build my own column model, that I can save and parse again, and in which to save only necessary properties.
This method is XSS vulnerable, as I am evaluating functions, but it is a working solution.
columnsApi: {
key: null,
grid: null,
newColumnModel: {
headerName: "",
width: 200,
valueGetter: "",
filter: 'text',
aggFunc: 'none',
filterParams: {apply: true}
},
setKey: function (key) {
this.key = key;
},
setGrid: function (grid) {
this.grid = grid;
},
format: function (columns) {
var format = [];
angular.forEach(columns, function (col) {
var colDef = {
width: col.actualWidth,
pinned: col.pinned,
hide: !col.visible
};
format.push(angular.extend(col.colDef, colDef));
});
return format;
},
getIDs: function (columns) {
var ids = [];
angular.forEach(columns, function (col) {
ids.push(col.colId);
});
return ids;
},
stringify: function (columns) {
return JSON.stringify(columns, function (key, value) {
if (typeof value === "function")
return "/Function(" + value.toString() + ")/";
return value;
});
},
parse: function (string) {
return JSON.parse(string, function (key, value) {
if (typeof value === "string" &&
value.startsWith("/Function(") &&
value.endsWith(")/")) {
value = value.substring(10, value.length - 2);
return eval("(" + value + ")");
}
return value;
});
},
add: function (column) {
if (this.grid === null) {
console.error("Assertion error: grid must not be null");
return;
}
if(column.aggFunc == 'none')
column.aggFunc = undefined;
var groups = this.get().groups;
var newColumns = this.format(getGridColumns(this.grid));
newColumns.push(column);
this.grid.api.setColumnDefs(newColumns);
this.setGroups(groups);
},
save: function () {
var self = this;
if (this.key === null) {
console.error("Assertion error: key must not be null");
return;
}
if (this.grid === null) {
console.error("Assertion error: grid must not be null");
return;
}
var savedOptions = {
columns: self.format(getGridColumns(self.grid)),
groups: self.getIDs(self.grid.columnApi.getRowGroupColumns()),
sorting: self.grid.api.getSortModel(),
filter: self.grid.api.getFilterModel()
};
localStorage.setItem(this.key, this.stringify(savedOptions));
},
// Get function uses "eval" - XSS vulnerable.
get: function () {
if (this.key === null) {
console.error("Assertion error: key must not be null");
return;
}
var options = localStorage.getItem(this.key);
if (options)
options = this.parse(options);
return options;
},
remove: function (field) {
if (this.grid === null) {
console.error("Assertion error: grid must not be null");
return;
}
var newColumns = this.format(getGridColumns(this.grid));
angular.forEach(newColumns, function (col, key) {
if (col.field == field)
newColumns.splice(key, 1);
});
this.grid.api.setColumnDefs(newColumns);
},
setGroups: function (groups) {
var self = this;
angular.forEach(groups, function (id) {
angular.forEach(getGridColumns(self.grid), function (col) {
if (col.colId == id)
self.grid.columnApi.addRowGroupColumn(col);
});
});
}
}
This solution was written for Ag-Grid 5 I believe, and thus I am not sure if it still holds.
I am currently working on an app using firebase and angularJS (ionic). Basically this is a car management app, so you have people sharing their cars with others. I tried to structure the data as flat as possible to be efficient. My issue here is that if without problem I can display the list of the car_id of the different cars shared with the logged user, I can't find a way to display the list of cars shared with the user displaying the year and the model.
Thank you in advance for your help !
{
"rules": {
"users": {
".write": true,
"$uid": {
".read": "auth != null && auth.uid == $uid"
},
"cars": {
"car_id":true,
"role":true // Owner, borower...
}
},
"cars": {
"car_id":true,
"model":true,
"year":true
}
}
}
carapp.controller("carsController", function($scope, $firebaseObject, $ionicPopup, $ionicHistory) {
$ionicHistory.clearHistory();
$scope.list = function() {
frbAuth = frb.getAuth();
if(frbAuth) {
var userObject = $firebaseObject(frb.child("users/" + frbAuth.uid));
userObject.$bindTo($scope, "user");
$scope.cars = frb.child("cars");
}}
$scope.createCar = function() {
$ionicPopup.prompt({
model: 'Create a new car',
inputType: 'text'
})
.then(function(result) {
if(result !== "") {
var newCar = $scope.cars.push({
model: result
})
var newCarId = newCar.key();
$scope.user.cars.push({car_id: newCarId, role: "owner" });
} else {
console.log("Action not completed");
}
});
}
});
<div class="list">
<a ng-repeat="car in user.cars" >
<h2>{{car.car_id}}</h2> ----> works fine !
<h2>{{car.model}}</h2> ----> How to get this working ?
<h2>{{car.year}}</h2> ----> How to get this working ?
</a>
</div>
In the users/ path, begin by storing the list of cars by index, instead of in an array. So your structure would be:
{
"users": {
"kato": {
"cars": {
"DeLorean": true
}
}
},
"cars": {
"DeLorean": {
model: "DeLorean",
year: "1975"
}
}
}
To join this using AngularFire, you have several approaches available. An AngularFire-only solution might look like this, taking advantage of $extend:
app.factory('CarsByUser', function($firebaseArray) {
return $firebaseArray.$extend({
$$added: function(snap) {
return new Car(snap);
},
$$updated: function(snap) {
// nothing to do here; the value of the index is not used
},
$$removed: function(snap) {
this.$getRecord(snap.key()).destroy();
},
// these could be implemented in a manner consistent with the
// use case and above code, for simplicity, they are disabled here
$add: readOnly,
$save: readOnly
});
var carsRef = new Firebase(...).child('cars');
function Car(snap) {
// create a reference to the data for a specific car
this.$id = snap.key();
this.ref = carsRef.child(this.$id);
// listen for changes to the data
this.ref.on('value', this.updated, this);
}
Car.prototype.updated = function(snap) {
this.model = data.model;
this.year = data.year;
}
Car.prototype.destroy = function() {
this.ref.off('value', this.meta, this);
};
function readOnly() { throw new Error('This is a read only list'); }
});
app.controller('...', function($scope, CarsByUser, authData) {
// authenticate first, preferably with resolve
var ref = new Firebase(...).child(authData.uid);
$scope.cars = CarsByUser($scope);
});
For a more sophisticated and elegant approach, one could utilize NormalizedCollection and pass that ref into the AngularFire array:
app.controller('...', function($scope, $firebaseArray) {
var ref = new Firebase(...);
var nc = new Firebase.util.NormalizedCollection(
ref.child('users/' + authData.uid),
ref.child('cars')
)
.select('cars.model', 'cars.year')
.ref();
$scope.cars = $firebaseArray(nc);
});
Using ExtJs 4.2 with MVC pattern
I am trying to make a custom model, store, proxy, reader, writer but am having problems getting it to work in the MVC pattern. I followed this example to extend a model and I can see it working only if it is not used in the MVC way.
My store refers to a model such as Contacts defined in the model property, then Contacts refers to custom model WakandaModel using the model property. But when I create my store which refers to Contacts none of the model properties or proxy defined in the custom WakandaModel is brought over to the stores model.
Here is my code, I have left comments in so you can see what I have attempted to try. Thanks for any help!
App Code
Ext.Loader.setConfig({
enabled : true,
paths : {
'Ext.ux' : "lib/extux",
'Wakanda' : "lib/extux/wakanda"
}
});
Ext.application({
name : 'SimplyFundraising',
autoCreateViewport : true,
requires : ['Ext.ux.Router', // Require the UX
'Ext.window.MessageBox'],
controllers : ['Contacts'],
});
Custom Model
Ext.define('Wakanda.Model', {
extend: 'Ext.data.Model',
idProperty: '__KEY',
stampProperty: '__STAMP',
defaultProxyType: 'wakanda',
onClassExtended: function(cls, data) {
// debugger;
// cls.apply(this)
// var parts = data.$className.split('.');
// var entity = parts[2]
// var catalog = this.prototype.getCatalog(entity),
// attributes = catalog.attributes;
// for (var i = 0, l = attributes.length; i < l; i++) {
// if (attributes[i].name === 'ID') {
// attributes[i].persist = false;
// }
// }
// attributes.push({name: this.prototype.idProperty});
// attributes.push({name: this.prototype.stampProperty});
// // data.fields = attributes;
// // debugger;
// //this.setFields(data.fields)
// // var mymodel = Ext.ModelManager.getModel(data.$className);
// debugger;
// Ext.appy(this);
// //this.superclass.superclass.$onExtended.apply(this, arguments);
},
getCatalog: function(className) {
var catalog;
Ext.Ajax.request({
async: false,
url: 'http://127.0.0.1:8081/cors/$catalog/' + className,
success: function(response) {
catalog = Ext.decode(response.responseText);
}
});
return catalog;
}
});
Custom proxy
Ext.define('Wakanda.Proxy', {
extend: 'Ext.data.proxy.Rest',
// alternateClassName: 'SimplyFundraising.data.WakandaProxy',
alias : 'proxy.wakanda',
sortParam: '$orderby',
filterParam: '$filter',
startParam: '$skip',
limitParam: '$top',
// groupersParam: '$group',
reader: 'wakanda',
writer: 'wakanda',
actionMethods: {
create : 'POST',
read : 'GET',
update : 'POST',
destroy: 'POST'
},
buildUrl: function(request) {
debugger;
var modelName = this.model.modelName,
operation = request.operation,
records = operation.records || [],
record = records[0],
id = record ? record.getId() : operation.id,
url = '/cors/' + modelName,
action = request.action;
if (this.appendId && id && (action === 'read' || action === 'destroy')) {
url += '(' + id + ')';
}
request.url = url;
// console.log("buildUrl", this, arguments, request.url);
if (action !== 'read') {
if (action === 'create') action = 'update';
else if (action === 'destroy') action = 'delete';
url = Ext.urlAppend(url, '$method=' + action);
}
if (this.noCache) {
url = Ext.urlAppend(url, Ext.String.format("{0}={1}", this.cacheString, Ext.Date.now()));
}
return url;
},
encodeSorters: function(sorters) {
var min = [],
length = sorters.length,
i = 0, sort = '';
for (; i < length; i++) {
sort += sorters[i].property + ' ' + sorters[i].direction + ' ';
}
return sort;
},
encodeFilters: function(filters) {
var min = [],
length = filters.length,
i = 0, filter = '';
for (; i < length; i++) {
filter += filters[i].property + ' eq ' + filters[i].value + '# ';
}
return filter;
}
});
Custom reader
Ext.define('Wakanda.reader', {
extend: 'Ext.data.reader.Json',
//alternateClassName: 'SimplyFundraising.data.WakandaReader',
alias : 'reader.wakanda',
root: '__ENTITIES',
totalProperty: '__COUNT',
getData: function(data) {
if (Ext.isObject(data) && !data[this.root]) {
data = [data];
}
return data;
}
});
Custom writer
Ext.define('Wakanda.writer', {
extend: 'Ext.data.writer.Json',
// alternateClassName: 'SimplyFundraising.data.WakandaWriter',
alias: 'writer.wakanda',
writeAllFields: false,
getRecordData: function(record) {
var isPhantom = record.phantom === true,
writeAll = this.writeAllFields || isPhantom,
nameProperty = this.nameProperty,
fields = record.fields,
data = {},
changes,
name,
field,
key;
if (writeAll) {
// console.log("getRecordData1", this, arguments);
fields.each(function(field){
if (field.persist) {
name = field[nameProperty] || field.name;
data[name] = record.get(field.name);
} else {
}
});
} else {
changes = record.getChanges();
// console.log("getRecordData2", this, arguments, changes);
for (key in changes) {
if (changes.hasOwnProperty(key)) {
field = fields.get(key);
name = field[nameProperty] || field.name;
data[name] = changes[key];
}
}
if (!isPhantom) {
data[record.idProperty] = record.getId();
data[record.stampProperty] = record.get(record.stampProperty);
}
}
return {'__ENTITIES': [data]};
}
});
Contacts Model
Ext.define('SimplyFundraising.model.Contact', {
extend : 'Wakanda.Model' ,
//constructor: function() {
//alert(“Going to call parent’s overriden constructor…”);
// this.callParent(arguments);
// return this;
// }
});
Contacts Store
Ext.define('SimplyFundraising.store.Contacts', {
extend : 'Ext.data.Store',
model : 'SimplyFundraising.model.Contact',
autoLoad : true,
autoSync : true,
// constructor: function() {
// this.model = Ext.create('SimplyFundraising.model.Contact')
//alert(“Going to call parent’s overriden constructor…”);
// this.callParent(arguments);
return this;
// }
});
Contacts Controller
Ext.define('SimplyFundraising.controller.Contacts', {
extend : 'Ext.app.Controller',
models : ['Contact'],
views : ['contacts.List', 'contacts.Edit'],
init : function() {
this.control({
'contactslist' : {
itemdblclick : this.editContact,
removeitem : this.removeContact
},
'contactslist > toolbar > button[action=create]' : {
click : this.onCreateContact
},
// 'contactsadd button[action=save]': {
// click: this.doCreateContact
// },
'contactsedit button[action=save]' : {
click : this.updateContact
}
});
},
list : function() {
// var mystore = Ext.StoreMgr.lookup('Contacts');
// var myContact = this.getModel('Contact')
// var User = this.getModel('User');
//debugger;
// var mystore = Ext.create('SimplyFundraising.store.Contacts')
// var myContact = this.getModel('Contact').create()
// var bb = myContact.create()
// var rr = Ext.create('SimplyFundraising.model.Contact')
var mystore = Ext.create('SimplyFundraising.store.Contacts')
debugger;
// mystore.proxy.api.read = users.proxy.api.read + '(17)'
//mystore.proxy.extraParams = { $expand: 'ContactType'};
mystore.load();
//var test = Ext.ModelManager.getModel('Contact');
// //var User = this.getContactModel();
// User.load(258, {
// success: function(user) {
// console.log("Loaded user 258: " + user.get('lastName'));
// }
// });
},
editContact : function(grid, record) {
var view = Ext.widget('contactsedit');
view.down('form').loadRecord(record);
this.addnew = false
},
removeContact : function(Contact) {
Ext.Msg.confirm('Remove Contact ' + Contact.data.lastName, 'Are you sure?', function(button) {
if (button == 'yes') {
this.getContactsStore().remove(Contact);
}
}, this);
},
onCreateContact : function() {
var view = Ext.widget('contactsedit');
this.addnew = true
},
// doCreateContact: function (button) {
// var win = button.up('window'),
// form = win.down('form'),
// values = form.getValues(),
// store = this.getContactsStore();
// if (form.getForm().isValid()) {
// store.add(values);
// win.close();
// }
// },
updateContact : function(button) {
var win = button.up('window'), form = win.down('form'), record = form.getRecord(), values = form.getValues(), store = this.getContactsStore();
if (form.getForm().isValid()) {
if (this.addnew == true) {
store.add(values);
} else {
record.set(values);
}
win.close();
}
}
});
I got this working now:
It looks like my custom wakanda model,proxy,reader,writer where not loading. Still a bit confused on how MVC references classes and loads files and instantiates classes, which I asked in other question.
Any way the fix was to add a requires to the custom wakanda model and proxy
For the Wakanda model add requires: ['Wakanda.proxy'],
For Wakanda proxy add requires: ['Wakanda.reader', 'Wakanda.writer'],
now the inheritance is working as expected.