How to programmatically create URLs with AngularJS - javascript

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.

Related

What's the most efficient way to build a URL from an object with an unknowable number of properties?

I'm trying to write a set of reusable functions to abstract a bunch of SharePoint's REST API web services which is probably unnecessary context but I always feel kind of weird framing these questions.
Anyway. The endpoints all take parameters and I've decided to feed the operation that does the AJAX call and handles the return a Javascript object (if there's a better way to do this, I'm still learning, totally open to suggestions) called "Options" that has as its properties the parameters, like so:
ApiHelper.prototype.getListData = function(options){
//Set the base endpoint Url.
var executeUrl = "/web/lists/getByTitle('"+options.list+"')";
//Determine if the URI will have parameters.
var params;
var paramList = [];
if(options.select.length || options.filter.length || options.expand.length || options.top.length){
params = true;
paramList.push(options.select,options.filter,options.expand,options.top);
}else{
params = false;
}
//The first two ops are super basic and don't take parameters, so I collapse them into a single line.
if(options.op == 'All'){return this.execute(executeUrl).then(function(data){if(data.d){return data.d;}else{throw "Something bad happened..."}});
}else if(options.op == 'Id'){executeUrl+='/Id';return this.execute(executeUrl).then(function(data){if(data.d){return data.d.Id;}else{throw "Something bad happened..."}});
}else if(options.op == 'Forms' || options.op == 'Views' || options.op == 'WorkflowAssociations'){
executeUrl+=options.op;
return this.execute(executeUrl).then(function(data){
if(data.d && data.d.results){
return new QueryResults(data.d.results);
} else {
throw "Something bad happened...";
}
});
}else{
if(params){
//No idea...
}else{
//If we're doing Items but without params...
executeUrl+='/items';
}
return this.execute(executeUrl).then(function(data){
if(data.d && data.d.results){
return new QueryResults(data.d.results);
} else {
throw "Something bad happened...";
}
});
}
For ease of use on the user, I wanted to make the parameters optional. Originally, I was doing something like this with the parameters:
if(typeof options.select === 'undefined'){options.select = '';}
if(typeof options.filter === 'undefined'){options.filter = '';}
if(typeof options.expand === 'undefined'){options.expand = '';}
if(typeof options.top === 'undefined'){options.top = '10000';}
And then constructing the URL with the parameters attached anyway, just empty. That... works, but it seems inelegant and with SharePoint, you know, I can never be sure it will work. So, my goal here is to look at the options object and if, for example, there's a select statement, then append $select=[whatever], but if not, then don't. Same for filter, expand, and top.
Don't forget to encode all the parts of the URL query
function param(obj) {
return Object.keys(obj).map(key => {
return [key, obj[key]].map(encodeURIComponent).join('=');
}).join('&');
}
You may also look at jQuery's $.param. It does basically the same and supports arrays. You can find the source here.
function getProps(obj){
var str='';
for(var prop in obj){
if(str)str+='&';
else str='?';
str+=prop+'='+obj[prop];
}
return str;
}
I hope this will help.

Url encoding for query params in Ember.js

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

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.

Retrieve specific hash tag's value from url

In raw Javascript, how would one go about checking that a specific hash tag exists in a url, then grab the value?
Example: http://www.example.com/index.html#hashtag1=value1&#hashtag2=value2
I want to be able to grab the value of either hashtag1 or hashtag2.
var HashSearch = new function () {
var params;
this.set = function (key, value) {
params[key] = value;
this.push();
};
this.remove = function (key, value) {
delete params[key];
this.push();
};
this.get = function (key, value) {
return params[key];
};
this.keyExists = function (key) {
return params.hasOwnProperty(key);
};
this.push= function () {
var hashBuilder = [], key, value;
for(key in params) if (params.hasOwnProperty(key)) {
key = escape(key), value = escape(params[key]); // escape(undefined) == "undefined"
hashBuilder.push(key + ( (value !== "undefined") ? '=' + value : "" ));
}
window.location.hash = hashBuilder.join("&");
};
(this.load = function () {
params = {}
var hashStr = window.location.hash, hashArray, keyVal
hashStr = hashStr.substring(1, hashStr.length);
hashArray = hashStr.split('&');
for(var i = 0; i < hashArray.length; i++) {
keyVal = hashArray[i].split('=');
params[unescape(keyVal[0])] = (typeof keyVal[1] != "undefined") ? unescape(keyVal[1]) : keyVal[1];
}
})();
}
Using it:
Check if a "hash key" is present:
HashSearch.keyExists("thekey");
Get the value for a hash key:
HashSearch.get('thekey');
Set the value for a hash key, and update the URL hash:
HashSearch.set('thekey', 'hey');
Remove a hash key from the URL:
HashSearch.remove('thekey');
Reload the hash into the local object:
HashSearch.load();
Push the current key value set to the URL hash:
HashSearch.push();
Note that when a key does not exist and you try to get it, it will returned undefined. However, a key could exist with no value -- for example #key=val&novalue where novalue is a key with no value. If you do HashSearch.get("novalue") it would also return undefined. In which case, you should use HashSearch.keyExists("novalue") to verify that it is indeed a key.
I use this, and it works just fine for me. It's a little adjusing to a line I picked up somewhere, I believe on SO.
getURLHashParameter : function(name) {
return decodeURI(
(RegExp('[#|&]' + name + '=' + '(.+?)(&|$)').exec(location.hash)||[,null])[1]
);
},
window.location.hash should give you what you want.
jQuery BBQ (back button and query) leverages the HTML5 hashchange event to allow simple, yet powerful bookmarkable #hash history. In addition, jQuery BBQ provides a full .deparam() method, along with both hash state management, and fragment / query string parse and merge utility methods.
In short: This library allows you to dynamically change a hash "query string" within your page and have the URL match. It also allows you to dynamically pull values and normalizes working with the "query string." Finally it will add the query strings into the history which allows the back button to function as a navigation between previous query hash values.
A good move for UX would be to check out a library like jQuery BBQ :)

Categories

Resources