Creating JavaScript API for first time - javascript

I am creating a commercial API for the first time for responsive webpages/web applications (mobile devices).
I am new and, sadly, working alone as well as new to Javascript (long complicated story).
I was just wondering if someone from the industry could offer their professional opinion on the following format of a "get" call:
var getSample = function(params) {
//Returns Object
return $.ajax({
url: URL + 'downloadQuadrat.php',
type: 'GET',
data: { 'projectID': params.pid, 'quadratID': params.qid },
dataType: dataType
});
}
Function call:
var printList = function(lid,options,get) {
var list = $("ul#"+lid);
var promise = get(options);
promise.promise().then(
function(response) {
var items = response;
list.empty();
$.each(items, function(item,details) {
var ul = $('<ul/>');
ul.attr('id', lid+'_'+details.ID);
var li = $('<li/>')
.text(details.ID)
.appendTo(list);
ul.appendTo(list);
$.each(details,function(key,value) {
var li = $('<li/>')
.text(key+': '+value)
.appendTo(ul);
});
});
}
);
}
Any input or guidance will be hugely appreciated.

I'm not a professional from the industry, per se, but there's a few things that I think would improve your code:
Format it according to common conventions. It's hard to see what your code is doing without proper indentation.
Just use $("#"+lid) instead of $("ul#"+lid). The ul at the beginning does not add any disambiguation because id attributes must be unique, and it just make it take longer to parse.
Ditch localstorage in this case. It's not supported on all browsers, and as far as I can tell, you don't need it here. Just directly use the data returned from the response.
Here is how I would change your code:
var printList = function(lid, options, get) {
var promise = get(options);
var list = $("#" + lid);
promise.success(function(response) {
var data = response;
list.empty();
$.each(data, function(item, details) {
var ul = $('<ul/>').attr('id', lid + '_' + details.ID);
var li = $('<li/>').text(details.ID).appendTo(list);
ul.appendTo(list);
$.each(details, function(key, value) {
var li = $('<li/>').text(key + ': ' + value).appendTo(ul);
});
});
});
}
EDIT: The edited version of your code looks fine to me, except for the minor ul# thing.

Some more suggestions to make your API a tad more professional looking:
1 - Namespacing
Use a namespace to keep your code packaged neatly in it's own space where it won't conflict with other function definitions on the page. Something like this to start with:
window.MyNamespace = {};
MyNamespace.get = function(qid, pid) {
//things
};
MyNamespace.anotherFunction = function() {
//other stuff
}
If your code starts getting bigger you can wrap the whole lot in a closure. Also you could define it all as class and then instantiate it once to make your code neater and allow you to store instance variables and call this.anotherFunction(). I can provide examples of those too if you like.
2 - API method signatures
Another thing I prefer to see is explicit arguments to functions rather than function get(params) style code. Making parameters explicit makes your code easier to read and understand and discourages ad-hoc hacks which is especially important when writing an API. It's a case of just because you can doesn't mean you should.
3 - Config
Try to move things like IDs and URLs into variables to start with to make your code a bit easier to reuse and work with.
Generally, Javascript developers are famous for looking at your code before they look at your API docs so anything you can do to make the API function names and argument names more expressive and self-documenting will help them.

Related

Simple auto-completion function in javascript

I am trying to get a simple auto-complete function to work based off the usernames that a 3rd party app provides.
The app outputs data in this general format:
"{
"UserA":{"IP":"XXX.XXX.XXX.XXX","ConnectTime":"/Date(1435769694510)/","LastAskSource":"","LastAskType":2,"Name":"UserA"},
"UserB":{"IP":"XXX.XXX.XXX.XXX","ConnectTime":"/Date(1435769694510)/","LastAskSource":"","LastAskType":2,"Name":"UserB"},
"UserC":{"IP":"XXX.XXX.XXX.XXX","ConnectTime":"/Date(1435769694510)/","LastAskSource":"","LastAskType":2,"Name":"UserC"}
}"
Now, I want to use the general auto-complete function listed below:
$("#chatEntryBox").autocomplete({
source: usersOnline.Name
});
I have defined the array as such:
OnlineUsers = data.userinfo;
for (var i in OnlineUsers) {
var user = OnlineUsers[i];
usersOnline = $.map(OnlineUsers, function(el) { return el; })
}
Now, this part above works the way I would expect. I can test in the console what the values are of the array by doing JSON.stringify(usersOnline[0].Name);
I have looked at the documentation on the website for Jquery... I just think I am missing something or misunderstanding. If someone could point me in the right direction that would be great.
The end goal would be typing the first few letters of the users name, and it auto-completing the rest.
I made a fiddle for your problem here.
Loop to take out names from json can be as simple as this
var usersOnline = $.map(input, function(el) { return el.Name; })
$( "#chatEntryBox" ).autocomplete({
source: usersOnline
});

Backbone.js updating a collection in bulk

I have a collection of objects which will be updated in bulk (not necessarily all at the same time, but more than one).
Therefore I need to send updates to the server in bulk (i.e. not one request for each updated object), and I need to do partial updates (i.e. only update objects which have changed).
Problems I have come across:
There is no save() method on a Collection (strange considering it has a fetch() method)
There is no built in tracking of models that have changed in a Collection since the last sync with the server.
Is there a clean, elegant way to achieve this?
I have researched and tried a few things, but all solutions are rather kludgy and over-complicated - which is unacceptable for a framework which is meant to simplify these sort of things.
Surely there is a decent way to do such a common thing in Backbone. Or should I use another framework e.g. Angular JS?
You can just build an array of the JSON representation of the models. Something like this.
Backbone.Collection.prototype.save = function()
{
var data = [];
for (var n = 0; n < this.length; ++n)
{
data.push(this.models[n].toJSON());
}
// save data through jQuery
console.log(data);
};
var col = new Backbone.Collection();
col.add({status: true});
col.add({status: false});
col.save();
You can add some model.hasChanged() check to prevent unnecessary savings.
Just to help anyone who finds this question, this is what I ended up using (based on #joconja's answer):
Backbone.Collection.prototype.saveChanges = function() {
var data = [];
for (var i = 0; i < this.length; ++i) {
if (this.models[i].hasChanged()) {
var changes = this.models[i].changedAttributes();
changes.id = this.models[i].get('id');
data.push(changes);
this.models[i].changed = {};
}
}
Backbone.ajax({
url: this.url,
method: 'PUT',
contentType: 'application/json',
data: JSON.stringify(data)
});
};

Reading SharePoint Taxonomy Term Store and getDefaultLabel(lcid)

My App reads the SharePoint Term Store and need to get the label associated with the user's language. I get the user's language and lcid, and then I read all the terms under a certain node in the taxonomy using this code:
... some code to get the Term Store, then Term Group, then Term Set, and finally startTerm
var tsTerms = startTerm.get_terms();
context.load(tsTerms);
context.executeQueryAsync(
function () {
var termsEnum = tsTerms.getEnumerator();
while (termsEnum.moveNext()) {
var currentTerm = termsEnum.get_current();
var termName = currentTerm.get_name();
var userLabel = currentTerm.getDefaultLabel(lcid);
var userLabelValue = userLabel.get_value();
console.log ("Label=", userLabel, userLabelValue)
... more code ...
In the while loop, I can get all the attributes of the term I need, except for the label. In other samples I found on the web, to get the default label, my userLabel object would be loaded in the context, then another context.executeQueryAsync is called. All that makes sense, but this would induce a lot of calls to the SharePoint server.
But, when I write to the console the userLabel object, is shows as type SP.Result, and when I open it, I see the label I want under the m_value. So there should be no need to go to the server again. However, the userLabelValue is returned as a 0 - obviously, the get_value() does not work. In MSDN documentation, a SP.Result object type is for internal use only. Is there any way to extract the data that it stores?
I have attached a picture of the console with the object expanded, where we clearly see the m_value = "Contrat", which is the label I need to get to.
Use SP.Taxonomy.Term.getDefaultLabel Method to get the default Label for this Term based on the LCID:
function getTermDefaultValue(termId,lcid,success,failure)
{
var context = SP.ClientContext.get_current();
var taxSession = SP.Taxonomy.TaxonomySession.getTaxonomySession(context);
var termDefaultValue = taxSession.getTerm(termId).getDefaultLabel(lcid);
context.executeQueryAsync(function() {
success(termDefaultValue);
},
failure);
}
Note: SP.Taxonomy.Term.getDefaultLabel method expects locale identifier
(LCID) for the label.
Usage
var layoutsRoot = _spPageContextInfo.webAbsoluteUrl + '/_layouts/15/';
$.getScript(layoutsRoot + 'sp.taxonomy.js',
function () {
var termId = 'dff82ab5-6b7a-4406-9d20-40a8973967dd';
getTermDefaultValue(termId,1033,printLabelInfo,printError);
});
function printLabelInfo(label)
{
console.log(String.format('Default Label: {0}',label.get_value()));
}
function printError(sender,args){
console.log(args.get_message());
}
I was facing the same problem and found a solution. Instead of using getDefaultLabel(lcid), use this:
termSet.getTerm(Termid).getAllLabels(lcid).itemAt(0).get_value();
This, in my opinion, does the same as 'getDefaultLabel' but it works. It may cause a little bit more load than the other function but this one works for me

Make Twitter Bootstrap's typead search within multiple fields

By default, the typeahead plugin makes use of a single data source in order to fetch the results. What I would like is for it to search within multiple fields, so if say, I have:
var items = [
{'title': 'Acceptable', 'description': 'Good, but not great'}
]
It will search on both the title and description fields, ideally via AJAX.
Is this possible with this plugin?
Typeahead does not support using JSON objects without two tweaks. There are few pull-requests in Github for this, and I have submitted one myself, but, currently, you must manually override select and render. Additionally, you must also override highlighter, matcher, sorter, and updater, but those can done via the options passed into the typeahead.
var typeahead = control.typeahead({ /* ... */ }).data('typeahead');
// manually override select and render
// (change attr('data-value' ...) to data('value' ...))
// otherwise both functions are exact copies
typeahead.select = function() {
var val = this.$menu.find('.active').data('value')
this.$element.val(this.updater(val)).change()
return this.hide()
};
typeahead.render = function(items) {
var that = this
items = $(items).map(function (i, item) {
i = $(that.options.item).data('value', item)
i.find('a').html(that.highlighter(item))
return i[0]
});
items.first().addClass('active')
this.$menu.html(items)
return this
};
If you need help with the other ones, then let me know, but the gist of it is:
control.typehead({
matcher: function (item) {
var lcQuery = this.query.toLowerCase();
return ~item.title.toLowerCase().indexOf(lcQuery)
|| ~item.description.toLowerCase().indexOf(lcQuery);
}
};
I also have a JFiddle example related to the pull request that I made, but the sorting functions do not exist in 2.3.1, or even 3.x without the pull request being accepted, so you will have to override sorter in its entirety to effectively repeat what I did with matcher above (checking both while sorting).
As for the AJAX call, you can override the source method in the passed in options to get AJAX functionality. By not returning to the source call, it assumes that the second parameter, process, will be invoked with results.
control.typehead({
minLength: 3,
source: function(query, process) {
$.ajax({
url: url + encodeURIComponent(query),
type: 'GET',
success: process,
error: function() { /* ... */ }
});
}
});
Typeahead added support for multiple field searching in v10.3
https://github.com/twitter/typeahead.js/pull/811
Usage:
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('title', 'description'),

Create a drop down from an xml file, with no repeated values. Do I need an array?

I am still learning javascript and xml and have recently been presented with an issue I'm sure is simple to solve. I'm hoping for some help on this if possible.
I have an xml file that is found here
http://mrblesid.com/blueprint/bookinglist.xml
I'm currently using this code to create a drop down list featuring the values from just one of the attributes "strArtistName"
$(document).ready(function(artists){
$.ajax({
type: "GET",
url: "bookinglist.xml",
dataType: "xml",
success: function(artists_list) {
var select = $('#mySelect');
$(artists_list).find('vw_ADM_BookingListNull[strArtistName]').each(function(){
var artists = $(this).attr('strArtistName');
select.append('<option value="'+artists+'">'+artists+'</option>');
});
select.children(":first").text("please make a selection").attr("selected",true);
}
});
});
This is then called into a dropdown via the following
<form>
<select id="mySelect">
<option>loading</option>
</select>
</form>
I would like to avoid repeating the artist names that are found for every entry, am I right to think I would need to use an array for this? If so how do I go about it?
The name selected from the list should populate a variable to use elsewhere in the report.
Any help would be greatly appreciates as I have deadlines looming.
Thanks in advance,
Mikey
An array will work. Update the main part to
var artistsArr = [];
$(artists_list).find('vw_ADM_BookingListNull[strArtistName]').each(function(){
var artists = $(this).attr('strArtistName');
if ($.inArray(artists, artistsArr) == -1) {
select.append('<option value="'+artists+'">'+artists+'</option>');
artistsArr.push(artists);
}
});
Some browsers don't support Array.indexOf, so you can use jQuery's inArray.
First off, you should batch the DOM insertions for performance reasons (You can also squeeze a little more performance out by using an array instead of pure string concatanation) Here is your success function with some performance optimizations as well as the check for duplicate artists:
function(artists_list) {
var select = $('#mySelect'),
html = [],
artistArray = [];
$(artists_list).find('vw_ADM_BookingListNull[strArtistName]').each(function(){
var artists = $(this).attr('strArtistName');
// Check the artistArray for a duplicate.
// This will only work in more recent browsers.
if (artistArray.indexOf(artists) === -1) {
html.push('<option value="'+artists+'">'+artists+'</option>');
artistArray.push(artists);
}
});
// Join the HTML array and add it to the select element
select.append(html.join(''))
.children(":first").text("please make a selection").attr("selected",true);
}

Categories

Resources