Url encoding for query params in Ember.js - javascript

I am using Ember.js, version 1.7.0-beta.1, in my latest project. I use the query params feature to make a list survive a hard refresh (e.g. after a reload, the selected items in the list are still selected).
I got a controller whom manages that:
export default Ember.ObjectController.extend({
queryParams: [{selectedFiles: 'files'}],
selectedFiles: Ember.A([]), //list of file ids
... //other props
actions: {
selectFile: function(file) {
//set or remove the file id to the selectedFiles property
}
});
It works awesome, but with one condition: the url is url-encoded:
Chrome & IE:
path/354?files=%5B"6513"%2C"6455"%2C"6509"%2C"6507"%2C"6505"%2C"6504"%2C"6511"%5D
FF (automaticly sets the brackets):
path/354?files="6513"%2C"6455"%2C"6509"%2C"6507"%2C"6505"%2C"6504"%2C"6511"]
Is there a way in Ember to decode the query-param-string to a more readible format? Maybe I could use the decodeURIComponent() function somewhere?
The desired output:
path/354?files=["6513","6455","6509","6507","6505","6504","6511"]

I had a very similar problem, and made it work by overriding serializeQueryParam and deserializeQueryParam in the route.
In the controller you would have:
queryParams: ['files'],
files: []
And in the route:
serializeQueryParam: function(value, urlKey, defaultValueType) {
if (defaultValueType === 'array') {
return value;
// Original: return JSON.stringify(value);
}
return '' + value;
},
and:
deserializeQueryParam: function(value, urlKey, defaultValueType) {
if (defaultValueType === 'array') {
var arr = [];
for (var i = 0; i < value.length; i++) {
arr.push(parseInt(value[i], 10));
}
return arr;
// Original: return Ember.A(JSON.parse(value));
}
if (defaultValueType === 'boolean') {
return (value === 'true') ? true : false;
} else if (defaultValueType === 'number') {
return (Number(value)).valueOf();
}
return value;
},
The url will then become something like:
?files[]=1&files[]=2&files[]=3
Which will then be a real array on the server side.
Take a look at this working example on jsbin.com

Related

React object property assignment only works the first time

For the following code, parameters are js objects whose structures are initialized as follows:
statePiece = {
field_name: { disabled: false, exampleValue: "arbitrary" },
field_name2: {
/* ... */
},
field_nameN: {
/* ... */
}
};
userField = "field_name_string";
sesarValues = {
format: "one2one",
selectedField: "latitude",
disabledSelf: true,
addField: 0
};
This function works correctly and returns the modified statePiece as returnTemp the first time a particular statePiece.field_name is modified
export let setUserField = (statePiece, userField, sesarValues) => {
console.log("set user field", userField, "set mappval", sesarValues);
var temp = { ...statePiece }; //(this.state.fields[each].mappedTo != null) ? (this.state.fields[userField].mappedTo) : [];
var XUnit = statePiece[userField];
if (typeof userField != "string") {
console.log("not string");
for (var each of userField) {
if (sesarValues) {
temp[each].mappedTo = sesarValues.selectedField;
temp[each].disabled = true;
} else {
temp[each].disabled = !temp[each].disabled;
delete temp[each].mappedTo;
}
}
} else {
//is string
console.log("is string");
console.log(XUnit);
if (sesarValues) {
if (XUnit.disabled === true) XUnit.disabled = false;
console.log("1");
console.log(XUnit);
XUnit.disabled = true;
console.log(XUnit);
XUnit.mappedTo = sesarValues.selectedField;
} else {
console.log("2");
temp[userField].disabled = !temp[userField].disabled;
delete temp[userField].mappedTo;
}
}
let returnTemp = { ...temp, [userField]: XUnit };
console.log("set UF debug ", returnTemp);
console.log(returnTemp["FACILITY_CODE"]);
return returnTemp;
};
But after that, changing the statePiece.userField.mappedTo value fails to alter the object property. Or at least alter it permanently. When I console log the returnTemp variable, I see the entry has lost its mappedTo entry(as should happen) without it being replaced with the new userField.
However, when I console.log(returnTemp[userField]) it shows the entry values with the expected mappedTo key: value pair.
Not sure what's going on here.
From the usage of userField, I can work out that it could be an Array or a String.
However you have done something curious with it in the following expression:
var XUnit = statePiece[userField];
Given userField is a String, the above expression is fine.
However, where it is an array, XUnit will be undefined.
Also doing the same where userField is an Array in the following line means that you're setting the userField.toString() as a key mapped to undefined.
let returnTemp = { ...temp, [userField]: XUnit };
I'd assign XUnit where the condition checks out that userField is a String and just return temp.
else {
//is string
var XUnit = statePiece[userField];
//...
}
return temp;

Ionic/Angular: Read and Write Array in Local Storage

I'm working with Ionic framework as part of an online course I'm taking to learn AngularJS and a great many other tools useful to a web developer. And, being the sort of advanced beginner type, I'm stuck. In this unit, we've learned to leverage local storage to persist data locally so we can get our favourite items even after the app is shut down. However, I have trouble getting that to work.
So here's what I've done:
The Failed Attempt
I can get data into local storage. And I can append data. I do this using this function:
$scope.favoriteData = $localStorage.getObject('favorites', '[]');
$scope.addFavorite = function (index) {
console.log('Current Favorites', $scope.favoriteData);
$scope.favoriteData = Object.keys($scope.favoriteData).map(function(k) { return $scope.favoriteData[k] });
console.log ($scope.favoriteData);
$scope.storeVar = $scope.favoriteData.push("'{id':" + index + '},');
console.log ($scope.favoriteData);
$localStorage.storeObject('favorites', $scope.favoriteData);
console.log('Added Favorite', $scope.favoriteData)
};
In local storage, this produces the following entry:
favorites: ["'{id':0},","'{id':1},"]
So far so good. However, this is useless. Because I need this object to have the following format:
favorites: [{'id':0}, {'id':1}]
and so on. Also, I should not be able to add duplicates. I have a kind of function for that elsewhere, but I am stuck on how to combine the two functions.
The function I have is this:
function (index) {
for (var i = 0; i < favorites.length; i++) {
if (favorites[i].id == index)
return;
}
favorites.push({
id: index
});
};
The problem with this is, I don't understand how it does what it does.
So please, help?
EDIT #1:
The Second Attempt
With the help of #Muli and #It-Z I'm working with the following code right now:
$scope.favoriteData = $localStorage.getObject('favorites', '[]');
$scope.addFavorite = function (index) {
console.log('Current Favorites', $scope.favoriteData);
$scope.favoriteData = Object.keys($scope.favoriteData).map(function(k) { return $scope.favoriteData[k] });
console.log ($scope.favoriteData);
for (var i = 0; i < favorites.length; i++) {
if (favorites[i].id == index) {
console.log ("Found duplicate id " + favorites[i].id);
return;
}
}
$scope.storeVar = $scope.favoriteData.push({id: index});
console.log ($scope.favoriteData);
$localStorage.storeObject('favorites', $scope.favoriteData);
console.log('Added Favorite', $scope.favoriteData)
};
However, this doesn't work because with a nonexistant key favorites, it doesn't work and gives me an error. So I need to implement a check if the key exists and if it doesn't, then it should create one. I've looked at this question, but it didn't work, mainly because I must use the following factory in services.jsto access local storage:
.factory('$localStorage', ['$window', function ($window) {
return {
store: function (key, value) {
$window.localStorage[key] = value;
},
get: function (key, defaultValue) {
return $window.localStorage[key] || defaultValue;
},
storeObject: function (key, value) {
$window.localStorage[key] = JSON.stringify(value);
},
getObject: function (key, defaultValue) {
return JSON.parse($window.localStorage[key] || defaultValue);
}
}
}])
So this is where I'm at right now. And I'm still stuck. Or again stuck. I don't know.
$localStorage handles serialization and deserialization for you so there's no need for $scope.favoriteData = $localStorage.getObject('favorites', '[]');
You can just call:
$scope.favoriteData = $localStorage.favoriteData || {/*Defaults object*/};
Same goes for saving data. use the dot notation.
Check the demo.
As for the duplicates: just handle them yourself like you would normally. when you're done call $localStorage.mySet = modifiedSet (modified set is standard JS object).
Note: this assumes you use ngStorage.
First of all, this line:
$scope.storeVar = $scope.favoriteData.push("'{id':" + index + '},');
Should be:
$scope.storeVar = $scope.favoriteData.push({id: index});
This is because in the original line you are pushing string into favoriteData while you wanted objects.
And if you want to check first for duplicates your can go with somthing like this:
$scope.favoriteData = $localStorage.getObject('favorites', []);
$scope.addFavorite = function (index) {
console.log('Current Favorites', $scope.favoriteData);
$scope.favoriteData = Object.keys($scope.favoriteData).map(function(k) { return $scope.favoriteData[k] });
console.log ($scope.favoriteData);
for (var i = 0; i < favorites.length; i++) {
if (favorites[i].id == index) {
console.log ("Found duplicate id " + favorites[i].id);
return;
}
}
$scope.storeVar = $scope.favoriteData.push({id: index});
console.log ($scope.favoriteData);
$localStorage.storeObject('favorites', $scope.favoriteData);
console.log('Added Favorite', $scope.favoriteData)
};

How to programmatically create URLs with AngularJS

Currently I am toying with the AngularJS framework. I'm using the $route service for deep linking into my single-page application.
Now I would like to navigate inside my application, say, by changing just the search part of the current URL. It is easy to do using the $location service in JavaScript, but how can I construct an href attribute from the current route with just the search part replaced?
Do I have to do it by hand or is there an AngularJS way for it?
Added:
Of course, the $location service has all methods to calculate such URLs. But I cannot use it because $location does also navigate the browser window to the new URL.
There is another complication with creating URLs by hand: One has to check whether the legacy #-method or the new History API is used. Depending on this, the URL has to include a #-sign or not.
Starting from v1.4 you can use $httpParamSerializer for that:
angular.module('util').factory('urlBuilder', function($httpParamSerializer) {
function buildUrl(url, params) {
var serializedParams = $httpParamSerializer(params);
if (serializedParams.length > 0) {
url += ((url.indexOf('?') === -1) ? '?' : '&') + serializedParams;
}
return url;
}
return buildUrl;
});
Usage:
To produce http://url?param1=value1&param2=value2_1&param2=value2_2 call it with:
urlBuilder('http://url', { param1: 'value1', param2: ['value2_1', 'value2_2'] });
Following Robert's answer, I found the functions in Angular.js. They are buildUrl, forEachSorted, and sortedKeys. builUrl also uses isObject and toJson functions, which are public, so they must be changed to angular.isObject and angular.toJson respectively.
function forEachSorted(obj, iterator, context) {
var keys = sortedKeys(obj);
for (var i = 0; i < keys.length; i++) {
iterator.call(context, obj[keys[i]], keys[i]);
}
return keys;
}
function sortedKeys(obj) {
var keys = [];
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
keys.push(key);
}
}
return keys.sort();
}
function buildUrl(url, params) {
if (!params) return url;
var parts = [];
forEachSorted(params, function (value, key) {
if (value == null || value == undefined) return;
if (angular.isObject(value)) {
value = angular.toJson(value);
}
parts.push(encodeURIComponent(key) + '=' + encodeURIComponent(value));
});
return url + ((url.indexOf('?') == -1) ? '?' : '&') + parts.join('&');
}
As you can see in the source code here (v.1.1.0):
https://github.com/angular/angular.js/blob/v1.1.0/src/ngResource/resource.js#L228
and here:
https://github.com/angular/angular.js/blob/v1.1.0/src/ngResource/resource.js#L247
Angularjs doesn't use internally encodeURIComponent for managing URI strings but neither make available the used functions. These functions are internal to the module (what I think is a pity).
If you want to manage your URIs in exactly the same way that AngularJS, maybe you can copy these functions from the source to your code and make your own service for that.

Searching for items in a JSON array Using Node (preferably without iteration)

Currently I get back a JSON response like this...
{items:[
{itemId:1,isRight:0},
{itemId:2,isRight:1},
{itemId:3,isRight:0}
]}
I want to perform something like this (pseudo code)
var arrayFound = obj.items.Find({isRight:1})
This would then return
[{itemId:2,isRight:1}]
I know I can do this with a for each loop, however, I am trying to avoid this. This is currently server side on a Node.JS app.
var arrayFound = obj.items.filter(function(item) {
return item.isRight == 1;
});
Of course you could also write a function to find items by an object literal as a condition:
Array.prototype.myFind = function(obj) {
return this.filter(function(item) {
for (var prop in obj)
if (!(prop in item) || obj[prop] !== item[prop])
return false;
return true;
});
};
// then use:
var arrayFound = obj.items.myFind({isRight:1});
Both functions make use of the native .filter() method on Arrays.
Since Node implements the EcmaScript 5 specification, you can use Array#filter on obj.items.
Have a look at http://underscorejs.org
This is an awesome library.
http://underscorejs.org/#filter
edited to use native method
var arrayFound = obj.items.filter(function() {
return this.isRight == 1;
});
You could try find the expected result is using the find function, you can see the result in the following script:
var jsonItems = {items:[
{itemId:1,isRight:0},
{itemId:2,isRight:1},
{itemId:3,isRight:0}
]}
var rta = jsonItems.items.find(
(it) => {
return it.isRight === 1;
}
);
console.log("RTA: " + JSON.stringify(rta));
// RTA: {"itemId":2,"isRight":1}
Actually I found an even easier way if you are using mongoDB to persist you documents...
findDocumentsByJSON = function(json, db,docType,callback) {
this.getCollection(db,docType,function(error, collection) {
if( error ) callback(error)
else {
collection.find(json).toArray(function(error, results) {
if( error ) callback(error)
else
callback(null, results)
});
}
});
}
You can then pass {isRight:1} to the method and return an array ONLY of the objects, allowing me to push the heavy lifting off to the capable mongo.

How do I remove all the extra fields that DOJO datastore adds to my fetched items?

When fetching an item from a DOJO datastore, DOJO adds a great deal of extra fields to it. It also changes the way the data is structure.
I know I could manually rebuild ever item to its initial form (this would require me to make updates to both JS code everytime i change my REST object), but there certainly has to be a better way.
Perhaps a store.detach( item ) or something of the sort?
The dojo.data API is being phased out, partly because of the extra fields. You could consider using the new dojo.store API. The store api does not add the extra fields.
I have written a function that does what you are looking to do. It follows. One thing to note, my function converts child objects to the { _reference: 'id' } notation. You may want different behavior.
Util._detachItem = function(item) {
var fnIncludeProperty = function(key) {
return key !== '_0'
&& key !== '_RI'
&& key !== '_RRM'
&& key !== '_S'
&& key !== '__type'
};
var store = item._S;
var fnCreateItemReference = function(itm) {
if (store.isItem(itm)) {
return { _reference: itm.id[0] };
}
return itm;
};
var fnProcessItem = function(itm) {
var newItm = {};
for(var k in itm) {
if(fnIncludeProperty(k)) {
if (dojo.isArray(itm[k])) {
// TODO this could be a problem with arrays with a single item
if (itm[k].length == 1) {
newItm[k] = fnCreateItemReference(itm[k][0]);
} else {
var valArr = [];
dojo.forEach(itm[k], function(arrItm) {
valArr.push(fnCreateItemReference(arrItm));
});
newItm[k] = valArr;
}
} else {
newItm[k] = fnCreateItemReference(itm[k]);
}
}
}
return newItm;
};
return fnProcessItem(item);
};
NOTE: this function is modified from what I originally wrote and I did not test the above code.

Categories

Resources