I'm dealing with a huge javascript codebase that I'm trying to reorganize. I'm not really an expert and I just started studying good javascript coding practices. So, one thing I'm trying to do is to divide all the code in modules. In this particular case I'm trying to create a module that would help me to optimize video embeds. I would like to pass the module an id and receive some html code or an image out of it.
I'm not putting the whole code here, but it's enough for the example:
var videoIframe = (function($) {
'use strict';
var id,
setVideoId = function(videoId) {
id = videoId;
console.log(id);
},
getVideoThumbnail = function(videoId) {
setVideoId(videoId);
},
test = function() {
console.log(id)
},
getVideoEmbedCode = function() {
};
return {
test: test,
getVideoThumbnail: getVideoThumbnail
};
})(jQuery);
In another module I assign it to two variables:
var video1 = videoIframe;
var video2 = videoIframe;
video1.getVideoThumbnail(123);
video2.getVideoThumbnail(456);
video1.test();
video2.test();
And, of course, I'm not getting what I expected. After the second getVideoThumbnail call, it always prints 456.
Doing some research I understood that I'm creating a singleton, a single instance, and I'm only changing values inside that instance. I think I need a constructor for my module, but I'm not sure how to create it in combination with the IIFE pattern. And is it the right approach?
And is it the right approach?
No. IIFEs are for things you want to do exactly once.
If you want to do something multiple times, then use a regular function and call it multiple times.
var videoIframe = (function($) {
function videoIframe() {
'use strict';
var id,
setVideoId = function(videoId) {
id = videoId;
console.log(id);
},
getVideoThumbnail = function(videoId) {
setVideoId(videoId);
},
test = function() {
console.log(id)
},
getVideoEmbedCode = function() {
};
return {
test: test,
getVideoThumbnail: getVideoThumbnail
};
}
return videoIframe;
})(jQuery);
var video1 = videoIframe();
var video2 = videoIframe();
video1.getVideoThumbnail(123);
video2.getVideoThumbnail(456);
video1.test();
video2.test();
The problem is that you are initializing object when assigning to videoIframe variable:
var videoIframe = (function($) {
// ...
})(jQuery);
You can try with:
var videoIframe = (function($) {
// ...
});
var video1 = videoIframe(jQuery);
var video2 = videoIframe(jQuery);
Just keep videoIframe as a function that returns your IIFE, instead of reusing the singleton. I kept the IIFE so the id and the inner functions keep being encapsulated and hence, not reachable if they're not in the returned interface object. If you are planning to create alot of these, it might be more efficient to just use a constructor and a prototype, so the inner functions don't get recreated for every instance.
var videoIframe = function() {
'use strict';
return (function() {
var id,
setVideoId = function(videoId) {
id = videoId;
console.log(id);
},
getVideoThumbnail = function(videoId) {
setVideoId(videoId);
},
test = function() {
console.log(id)
},
getVideoEmbedCode = function() {
};
return {
test: test,
getVideoThumbnail: getVideoThumbnail
};
}());
};
I did some minor modification. Hope it will be helpful
var videoIframe = (function($) {
'use strict';
var id;
function _setVideoId(videoId) {
id = videoId;
alert(id);
};
function _getVideoThumbnail(videoId) {
_setVideoId(videoId);
};
function _test(){
console.log(id)
}
function _getVideoEmbedCode() {
};
return {
test: _test,
getVideoThumbnail: _getVideoThumbnail
};
})(jQuery);
Now you can call like this
videoIframe.getVideoThumbnail(123);
videoIframe.getVideoThumbnail(561);
jsfiddle
Related
I have a problem with an indexeddb query with index when running on IOS devices.
$.indexedDB(dbName).objectStore(tablename).index("INDICE").each(function(itemLocal) {
itemLocal.delete();
}, [VALORINDICE]).then(function() {
callback();
}, function() {
console.log("error");
});
The problem is if there is more than one record that matches the index, it does not eliminate them, it eliminates the first one and leaves. But if for example I put console.log (itemLocal) instead of itemLocal.delete() if it shows all those that match the index. Any suggestions of something that may be leaking?
I have tried with this code and I get the same error(code without api jquery)
var request = indexedDB.open(DATABASE_NAME);
request.onsuccess = function(event) {
var db = request.result;
var transaction = db.transaction(["TABLE"], "readwrite");
var table = transaction.objectStore("TABLE");
var index = table.index("INDEX");
var req = index.openCursor();
req.onsuccess = function() {
var cursor = req.result;
if (cursor) {
console.info(cursor.value);
cursor["delete"]();
cursor["continue"]();
}
};
req.onerror = function(e) {
console.error(e, req);
};
};
request.onerror = function(e) {
console.error(e, request);
};
I've implemented repository pattern with two entity managers,
mainManager is for read only and delete, and updateManager is used for edit and add new entities. I use createEmptyCopy() to create updateManager.
Before i update an entity i export the entity from mainManager and import into the updateManager, after the change i call to updateManager.saveChanges() method.
I've noticed that i get back the updated entities in the promise response. i wonder what is the best practice to import those entities back into the mainManager?
here is my code:
function ($q, $http, entityManagerFactory) {
var self = this;
self.mainManager = entityManagerFactory.newManager();
self.updateManager = entityManagerFactory.newManager();
self.saveChanges = function () {
return self.updateManager.saveChanges();
};
self.rejectChanges = function() {
self.updateManager.rejectChanges();
};
self.getDomains = function () {
self.mainManager.clear();
var query = new breeze.EntityQuery()
.from('Domains')
.orderBy('name');
return self.mainManager.executeQuery(query);
};
self.createEmptyDomain = function () {
var domain = self.updateManager.createEntity('Domain');
return domain;
};
self.editDomain = function(domain) {
var exported = self.mainManager.exportEntities([domain]);
return self.updateManager.importEntities(exported).entities[0];
}
self.addDomain = function (domain) {
self.updateManager.addEntity(domain);
return self.updateManager.saveChanges();
};
self.deleteDomain = function (domain) {
domain.entityAspect.setDeleted();
var deferred = $q.defer();
self.mainManager.saveChanges().then(
function(data) {
deferred.resolve(data);
},
function (reason) {
console.log(reason);
self.mainManager.rejectChanges();
deferred.reject(reason);
});
return deferred.promise;
};
}
Right now i'm calling mainManager.clear() and get the data again from the server as you can see above in getDomains function.
But i think this is too expansive, why call the server if i already have the updated entities from the saveChanges promise?
i've also tried to import those entities back to mainManager using:
mainManager.importEntities(data.entities, { mergeStrategy: breeze.MergeStrategy.OverwriteChanges });
but i get an internal null breeze exception:
TypeError: Cannot read property 'forEach' of undefined
at EntityManager.proto.importEntities (breeze.debug.js:13081)
at self.importEntities (domain-list.service.js:22)
at domain-list.controller.js:70
at processQueue (angular.js:13170)
at angular.js:13186
at Scope.promises.$get.Scope.$eval (angular.js:14383)
at Scope.promises.$get.Scope.$digest (angular.js:14199)
at Scope.promises.$get.Scope.$apply (angular.js:14488)
at done (angular.js:9646)
at completeRequest (angular.js:9836)
the error is from this line breeze.debug.js:13081
13080: var tempKeyMap = {};
13081: json.tempKeys.forEach(function (k) {
13082: var oldKey = EntityKey.fromJSON(k, that.metadataStore);
13083: // try to use oldKey if not already used in this keyGenerator. 13084: tempKeyMap[oldKey.toString()] = new EntityKey(oldKey.entityType,
13085: that.keyGenerator.generateTempKeyValue(oldKey.entityType, oldKey.values[0]));
13086: });
var exportData = updateManager.exportEntities(data.entities, false);
mainManager.importEntities(exportData,
{ mergeStrategy: breeze.MergeStrategy.OverwriteChanges });
I need to create an about: page, to display addon options. I have seen ti done before, but there seems to be no option in the SDK that allows you to do that.
Is there another way I could let users type about:pagename and get to my page?
I would prefer not to redirect all tabs with a URL of about:pagename to another options page.
Thanks in advance
This is the index.js file for a restartless add-on developed using jpm:
const { Cc, Ci, Cr, Cu, Cm, components } = require("chrome");
Cm.QueryInterface(Ci.nsIComponentRegistrar);
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
// globals
var factory;
const aboutPage_description = 'This is my custom about page';
const aboutPage_id = '6c098a80-9e13-11e5-a837-0800200c9a66'; // make sure you generate a unique id from https://www.famkruithof.net/uuid/uuidgen
const aboutPage_word = 'foobar';
const aboutPage_page = Services.io.newChannel('data:text/html,hi this is the page that is shown when navigate to about:foobar', null, null);
function AboutCustom() {};
AboutCustom.prototype = Object.freeze({
classDescription: aboutPage_description,
contractID: '#mozilla.org/network/protocol/about;1?what=' + aboutPage_word,
classID: components.ID('{' + aboutPage_id + '}'),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIAboutModule]),
getURIFlags: function(aURI) {
return Ci.nsIAboutModule.ALLOW_SCRIPT;
},
newChannel: function(aURI) {
let channel = aboutPage_page;
channel.originalURI = aURI;
return channel;
}
});
function Factory(component) {
this.createInstance = function(outer, iid) {
if (outer) {
throw Cr.NS_ERROR_NO_AGGREGATION;
}
return new component();
};
this.register = function() {
Cm.registerFactory(component.prototype.classID, component.prototype.classDescription, component.prototype.contractID, this);
};
this.unregister = function() {
Cm.unregisterFactory(component.prototype.classID, this);
}
Object.freeze(this);
this.register();
}
exports.main = function() {
factory = new Factory(AboutCustom);
};
exports.onUnload = function(reason) {
factory.unregister();
};
Basically it registers a custom about page that will be loaded when you access about:foobar. The loaded page is just a line of text.
This is how it looks like:
You can see a working example here: https://github.com/matagus/about-foobar-addon
I think this is a better solution if you are using the addons-sdk:
Credit goes here:
https://stackoverflow.com/a/9196046/1038866
var pageMod = require("page-mod");
pageMod.PageMod({
include: data.url("options.html"),
...
});
var tabs = require("tabs");
tabs.open(data.url("options.html"));
But there are other ways. You could take a look at the Scroll to Top addon which implements this: https://addons.mozilla.org/firefox/addon/402816
I am building an advanced search UI similar to the TFS query builder web interface. Using knockout for the client side implementation and have everything more or less working except the final validation to make certain required items are basically selected. It sort-of works as far as giving me a validation error if I select an item and then de-select the item. Which is fine, but I would like to have the form validate when hitting the search button.
I am pretty sure I need to make use of the ko.validatedobservable method, I'm just not sure exactly how. Anyway, I have a fiddle to look at: http://jsfiddle.net/sstolp/uXBSA/ if anyone has the time or inclination to help me out. I would deeply appreciate it.
Thank you for your time.
scvm.SearchLine = function () {
var self = this;
self.selectedField = ko.observable().extend({ required: true });
self.selectedOperator = ko.observable().extend({ required: true });
self.firstdate = ko.observable(new Date());
self.lastdate = ko.observable(new Date());
self.thedate = ko.observable(new Date());
return self;};
scvm.Criteria = function () {
var self = this,
lines = ko.observableArray([]),
// Put one line in by default
loadInitialData = function () {
lines.push(new scvm.SearchLine());
},
rowcount = ko.computed(function () {
return lines().length;
}),
// Operations
addLine = function () {
lines.push(new scvm.SearchLine());
},
removeLine = function (line) {
lines.remove(line);
},
search = function () {
var data = $.map(lines(), function (line) {
return line.selectedField() ? {
selectedField: line.selectedField().searchfield,
selectedOperator: line.selectedOperator().name,
} : undefined
});
alert("Send to server: " + JSON.stringify(data));
},
clear = function () {
lines.removeAll();
};
return {
lines: lines,
loadInitialData: loadInitialData,
rowcount: rowcount,
addLine: addLine,
removeLine: removeLine,
search: search,
clear: clear
};
}();
Yes, all your SearchLine objects must be wrapped into ko.validatedObservable. Also you should implement computed property which will check isValid() for each criteria line and return global validity flag.
scvm.SearchLine = function () {
var self = this;
self.selectedField = ko.observable().extend({ required: true });
self.selectedOperator = ko.observable().extend({ required: true });
self.firstdate = ko.observable(new Date());
self.lastdate = ko.observable(new Date());
self.thedate = ko.observable(new Date());
return ko.validatedObservable(self);
};
scvm.Criteria = function () {
// ...
return {
lines: lines,
loadInitialData: loadInitialData,
rowcount: rowcount,
addLine: addLine,
removeLine: removeLine,
search: search,
clear: clear,
// new property that indicates validity of all lines
linesValid: ko.computed(function(){
var items = lines();
for (var i = 0, l = items.length; i < l; i++)
if (!items[i].isValid()) return false;
return true;
})
};
}();
This new property can be used in enable binding of you "Search" button:
<input type="button"
data-bind="enable: linesValid, click: search"
title="Clicking this button will run a search."
value="Search" />
I've modified your fiddle. Take a look: http://jsfiddle.net/ostgals/uXBSA/8/
Update:
Also we should slightly modify Criteria.search method, since our line array contains observables rather than objects:
//...
search = function () {
var data = $.map(lines(), function (line) {
line = ko.utils.unwrapObservable(line);
return line.selectedField() ? {
selectedField: line.selectedField().searchfield,
selectedOperator: line.selectedOperator().name,
} : undefined
});
alert("Send to server: " + JSON.stringify(data));
},
//...
I am trying using code
// This is an active module of the goelvivek (8) Add-on
exports.main = function() {
var httpRequestObserver =
{
observe: function(subject, topic, data)
{
if (topic == "http-on-examine-response") {
if(console)
console.log(data);
}
}
};
var {Cc, Ci, Cr} = require("chrome");
var observer = require("observer-service");
observerService = Components.classes["#mozilla.org/observer-service;1"].
getService(Components.interfaces.nsIObserverService);
observerService.addObserver(httpRequestObserver, "http-on-examine-response", false);
};
but line console.log(data); is not printing any thing in console log. why ?
In addition to the issue noted by Nickolay, an observer needs to implement a QueryInterface() function (typically by means of XPCOMUtils.generateQI()). Here is how one would do it with the Add-on SDK:
var {Cc, Ci, Cr, Cu} = require("chrome");
var {XPCOMUtils} = Cu.import("resource://gre/modules/XPCOMUtils.jsm");
var httpRequestObserver =
{
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
...
};
However, since you already require observer-service package, it would be easier to use it:
var observer = require("observer-service");
observer.add("http-on-examine-response", onHttpRequest);
function onHttpRequest(subject, data)
{
...
}
The downside of this approach is that observer-service is an internal package and its API might change in future Add-on SDK versions.
Is it the real snippet? You should see an error about Components being undefined in the Error Console. Either get it from require('chrome') or use the object from require("observer-service").