Can my javascript memoization be more terse? - javascript

I'm trying to store a reference in my javascript to a page element that will be interacted with frequently and using this code:
var breadcrumbBar = null;
function getBreadcrumbBarElement() {
if (breadcrumbBar === null) {
breadcrumbBar = document.getElementById(breadcrumbBarElementId);
}
return breadcrumbBar;
}
However, I'm wondering if I can be more terse or idiomatic than this (and as a result improve my javascript more generally...)?

The idiomatic way is to use the logical or operator:
var breadcrumbBar = null;
function getBreadcrumbBarElement() {
return breadcrumbBar || (breadcrumbBar = document.getElementById(breadcrumbBarElementId));
}
or, to make this a bit more generic:
var elementCache = {}
function getCached(id) {
return elementCache[id] || (elementCache[id] = document.getElementById(id));
}

Or in order to encapsulate the cache into function:
function getCached(id) {
if (!getCached.elementCache) getCached.elementCache = {};
return getCached.elementCache[id] || (getCached.elementCache[id] = document.getElementById(id));
}

Related

Javascript & knockoutjs: how to refactor the following code to be able to access the properties outside the function

Im struggling to find a way to get the properties Override & Justification available outside of the function. The code is:
self.CasOverridesViewModel = ko.observable(self.CasOverridesViewModel);
var hasOverrides = typeof self.CasOverridesViewModel === typeof(Function);
if (hasOverrides) {
self.setupOverrides = function() {
var extendViewModel = function(obj, extend) {
for (var property in obj) {
if (obj.hasOwnProperty(property)) {
extend(obj[property]);
}
}
};
extendViewModel(self.CasOverridesViewModel(), function(item) {
item.isOverrideFilledIn = ko.computed( function() {
var result = false;
if (!!item.Override()) {
result = true;
}
return result;
});
if (item) {
item.isJustificationMissing = ko.computed(function() {
var override = item.Override();
var result = false;
if (!!override) {
result = !item.hasAtleastNineWords();
}
return result;
});
item.hasAtleastNineWords = ko.computed(function() {
var justification = item.Justification(),
moreThanNineWords = false;
if (justification != null) {
moreThanNineWords = justification.trim().split(/\s+/).length > 9;
}
return moreThanNineWords;
});
item.isValid = ko.computed(function() {
return (!item.isJustificationMissing());
});
}
});
}();
}
I've tried it by setting up a global variable like:
var item;
or
var obj;
if(hasOverrides) {...
So the thing that gets me the most that im not able to grasp how the connection is made
between the underlying model CasOverridesviewModel. As i assumed that self.CasOverridesViewModel.Override() would be able to fetch the data that is written on the screen.
Another try i did was var override = ko.observable(self.CasOverridesViewModel.Override()), which led to js typeError as you cannot read from an undefined object.
So if anyone is able to give me some guidance on how to get the fields from an input field available outside of this function. It would be deeply appreciated.
If I need to clarify some aspects do not hesitate to ask.
The upmost gratitude!
not sure how far outside you wanted to go with your variable but if you just define your global var at root level but only add to it at the moment your inner variable gets a value, you won't get the error of setting undefined.
var root = {
override: ko.observable()
};
root.override.subscribe((val) => console.log(val));
var ViewModel = function () {
var self = this;
self.override = ko.observable();
self.override.subscribe((val) => root.override(val));
self.load = function () {
self.override(true);
};
self.load();
};
ko.applyBindings(new ViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>

How to extend ElementFinder object in ProtractorJS?

Durring some experiments with protractorJS i noticed that there is no easy way to extend (inherit) ElementFinder object from protractor to add own functions.
For example, i want to create object Checkbox, that would have additional method - check() - should switch checkbox depending on result of isSelected().
I come up with the code -
var ElementFinder = require('protractor/lib/element.js').ElementFinder;
var ElementArrayFinder = require('protractor/lib/element.js').ElementArrayFinder;
class CheckBox extends ElementFinder {
constructor(loc) {
var getWebElements = function () {
var ptor = browser;
var locator = loc;
return ptor.waitForAngular().then(function() {
if (locator.findElementsOverride) {
return locator.findElementsOverride(ptor.driver, null, ptor.rootEl);
} else {
return ptor.driver.findElements(locator);
}
});
}
var ArrayFinderFull = new ElementArrayFinder(browser, getWebElements, loc);
super(browser, ArrayFinderFull);
}
check() {
return this.isSelected().then(selected => selected? this.click() : null)
}
}
But getWebElements is copy-paste from protractor/element.js -
https://github.com/angular/protractor/blob/3.1.0/lib/element.js#L131
This copy-paste flustrating me. I think there should be more proper way to extend ElementFinder.
Does anyone inherited ElementFinder in protractorJS?
I'm not sure this would help, but here is something we did recently to have a takewhile() method available on an ElementArrayFinder. We've put the following into onPrepare():
protractor.ElementArrayFinder.prototype.takewhile = function(whileFn) {
var self = this;
var getWebElements = function() {
return self.getWebElements().then(function(parentWebElements) {
var list = [];
parentWebElements.forEach(function(parentWebElement, index) {
var elementFinder =
protractor.ElementFinder.fromWebElement_(self.ptor_, parentWebElement, self.locator_);
list.push(whileFn(elementFinder, index));
});
return protractor.promise.all(list).then(function(resolvedList) {
var filteredElementList = [];
for (var index = 0; index < resolvedList.length; index++) {
if (!resolvedList[index]) {
break;
}
filteredElementList.push(parentWebElements[index])
}
return filteredElementList;
});
});
};
return new protractor.ElementArrayFinder(this.ptor_, getWebElements, this.locator_);
};
And now we can use takewhile on the result of element.all():
element.all(by.repeater("row in rows")).takewhile(function (elm) {
return elm.getText().then(function (text) {
return some_condition_to_be_true;
});
});
Now it is much simplier to extend ElementFinder, i calling this - page fragments.
I even created lib to solve this issue (PRs welcome!) - https://github.com/Xotabu4/protractor-element-extend
For now it only works with ElementFinder, but i want to be able to extend
ElementArrayFinders as well (planned for 2.0.0 version)
UPDATE
Support for ElementArrayFinder inheritance is added.

Shorthand creation of object chains in Javascript

What I am interested to know is if there is a shorter way of achieving the following:
App.Plugins = App.Plugins || {};
App.Plugins.SomePlugin = App.Plugins.SomePlugin || {};
App.Plugins.SomePlugin.Models = App.Plugins.SomePlugin.Models || {};
App.Plugins.SomePlugin.Views = App.Plugins.SomePlugin.Views || {};
App.Plugins.SomePlugin.Collections = App.Plugins.SomePlugin.Collections || {};
As far as I know, this format is fine, please someone let me know if I'm mistaken, I'm just wondering if there is some nicer way of doing this initial setup on my singleton.
Thanks in advance...
You could do the following:
function defaults(obj, prop) {
return obj[prop] = obj[prop] || {};
}
defaults(App, 'Plugins');
defaults(App.Plugins, 'SomePlugin');
defaults(App.Plugins.SomePlugin, 'Models');
defaults(App.Plugins.SomePlugin, 'Views');
defaults(App.Plugins.SomePlugin, 'Collections');
I don't understand exactly why you do this
Anyway a shorter way to write that is:
App={
Plugins:{
SomePlugin:{
Models:{},
Views:{},
Collections:{}
}
}
}
then considering the function
function defaults(obj, prop) {
return obj[prop] = obj[prop] || {};
}
would return an error using
defaults(App.Plugins.AnotherPlugin,'Models')
checking this is a pain:
var x={};
if(App&&
App.Plugins&&
App.Plugins.AnotherPlugin&&
App.Plugins.AnotherPlugin.Models
){
x=App.Plugins.AnotherPlugin.Models
}
console.log(x);
A solution is
var x={};
try{x=App.Plugins.AnotherPlugin.Models}catch(e){}
console.log(x)
this gives you no errors
but you can't set it the easy way.
EDIT
comment answer
Then you should start checking at the point where nothing is certain. In your case you just need to check if anotherPlugin exists.you probably already have App & App.Plugins.so you don't need App=App||{}, but only App.Plugins.AnotherPlugin
!App.Plugins.AnotherPlugin||App.Plugins.AnotherPlugin=NewPlugin
or a function
function addPlugin(name,newPlugin){
!App.Plugins[name]||App.Plugins[name]=newPlugin
}
An define your own standards... I mean why return an object if it does not exist?
if it does not exist you can't do anything anyway.
and again the biggest problem is always to check if it exists... and like i already described above it is try catch.
EDIT2
check this function.
function def(a,b,c,d){
c=b.split('.');
d=c.shift();
a[d]||(a[d]={});
!(c.length>0)||def(a[d],c.join('.'));
}
usage:
var A={};
def(A,'B.C.D.E.F')
//this transforms your {}
//to
A:{
B:{
C:{
D:{
E:{
F:{
}
}
}
}
}
}
http://jsfiddle.net/5NgWL/
to create your plugin:
var App={}
def(App,'Plugins.SomePlugin.Models')
def(App.Plugins.SomePlugin,'View')
// &/or
def(App,'Plugins.SomePlugin.Collections')
May be use it
function namespace(path) {
var segments = path.split('.'),
result = {};
function define(name, obj) {
if (name === '.') {
return obj;
}
!obj[name] && (obj[name] = {});
return define(segments.shift(), obj[name]);
}
segments.push('.'); //add stop symbol;
define(segments.shift(), result);
return result;
}
namespace('App.Plugins.SomePlugin.Collections').controller = function() {}

Embedding an anonymous function inside of another anonymous function

I have a hash called options. The problem that I'm facing is that options['beforeOpen'] might already be a function, in which case I don't want to overwrite it. I'd like to instead call it then call another function that needs to be called every time
In this example the method that needs to be called every time is methodThatINeedToDo. I thought the code below would accomplish this but it's not working as I expected.
function methodThatINeedToDo(){alert('maintenance');}
var options = {beforeOpen: function(){alert('first');}}
if(typeof options['beforeOpen'] == "function"){
options['beforeOpen'] = function(){options['beforeOpen'].call(); methodThatINeedToAddToDo();}
} else {
options['beforeOpen'] = methodThatINeedToDo;
}
The problem is that within the function you're defining to override options['beforeOpen'], you're using options['beforeOpen'], which by that time has been overwritten!
You need to cache it and use the cached value within your new function:
var cachedBeforeOpen = options.beforeOpen;
if (typeof cachedBeforeOpen == "function") {
options.beforeOpen = function() {
cachedBeforeOpen.call();
methodThatINeedToDo();
};
} else {
options.beforeOpen = methodThatINeedToDo;
}
Simply always call methodThatINeedToDo, since you want to and in there check to see if you should call your options method:
function methodThatINeedToDo(){
options.beforeOpen && options.beforeOpen();
alert('maintenance');
}
That really smells like the wrong solution. Why not Publish/Subscribe pattern?
Here's a little example: http://jsfiddle.net/ajyQH/
$(function() {
var yourObj = { yourFct : [] };
$('#btn').click(function() {
yourObj.yourFct.push(function() {
$('#testibert').append($('<p>').text('hallo'));
});
});
$('#btn_exec').click(function() {
var len = yourObj.yourFct.length;
for(var i = 0; i < len; i++) {
yourObj.yourFct[i]();
}
});
});
var oldCall = options['beforeOpen'];
var newCall = function(){
oldCall();
methodThatINeedToAddToDo();
};
options['beforeOpen'] = newCall;

Can I put a jquery handler in a constructor?

I'm seeing if I can make some object oriented javascript and I have the following code.
When I went to move my jquery event handler into the constructor I became confused because now I have two this variables...
Am I approaching this incorrectly or is there a way to make it work?
function Dropdown(ddlname) {
this.Value = 0;
this.Selected = false;
this.DDL = ddlname;
this.Limited = false;
this.SelectLast = function () {
$(this.DDL + ' option:last').attr('selected', 'selected');
}
$(ddlname).change(function () {
var v = $(this).val(); // <== ?
if (typeof v == 'number') {
this.Value = v; // <== ?
this.Selected = true; // <== ?
}
});
return true;
};
You need to assign "this" from the context of your constructor to a local variable to be able to reference it from within your jquery event handler.
function Dropdown(ddlname) {
this.Value = 0;
this.Selected = false;
this.DDL = ddlname;
this.Limited = false;
var hold = this;
this.SelectLast = function () {
$(hold.DDL + ' option:last').attr('selected', 'selected');
}
$(ddlname).change(function () {
var v = $(this).val(); // <== ?
if (typeof v == 'number') {
hold.Value = v; // <== ?
hold.Selected = true; // <== ?
}
});
return true;
};
One trick i learnt from Marcelo Ruiz of DataJS team from microsoft is as follows:
function Dropdown(ddlname)
{
var that = this;
//rest of your code. now there is no confusion of this since you have that :)
};
Not sure if this would help you. but just a trick i learned.
Yes you may, but you will need to call the class in the document ready function. I'm pretty sure it's bad practice.
You should consider passing the $ to the constructor or making a jQuery extension.

Categories