In the following implementation of a hypothetical navigation module the module object returns properties such as isOverBinded or isNavTurnedOff which basically return the consequential value of other methods.
This methods are then utilised in the test cases to check whether a property call has caused its expected consequence.
Should these methods be kept or the original method in question return the consequential values and the same method to be used in the test case?
Currently the code is:
var navModule = (function(element) {
var nav = {};
var navHTMLobjs = {
navList : element,
listItems : element.find('li'),
listLinks : element.find('a')
};
nav.bindOver = function() {
navHTMLobjs.navList.on('mouseover mouseout', 'li a', function(e) {
if (e.type == 'mouseover') {
$(this).addClass('over');
}
if (e.type == 'mouseout') {
$(this).removeClass('over');
}
});
};
nav.isOverBinded = function(){
return navHTMLobjs.navList.data('events').hasOwnProperty('mouseover')
&& navHTMLobjs.navList.data('events').hasOwnProperty('mouseout');
};
nav.turnOff = function() {
navHTMLobjs.navList.off('mouseover mouseout');
};
nav.isNavTurnedOff = function() {
return !navHTMLobjs.navList.data.hasOwnProperty('events');
};
nav.init = function() {
this.bindOver();
};
return nav;
});
var myNav = new navModule($('#nav'));
/// Test cases:
module('Navigation module');
test('Binding total', function() {
myNav.init();
equal(myNav.isOverBinded(), true, "Does the init function attach all events?");
});
test('Unbinding total', function() {
myNav.turnOff();
equal(myNav.isNavTurnedOff(), true, "Does the cancel function correctly unbind events?");
});
For example should I change nav.bingOver to be:
nav.bindOver = function() {
navHTMLobjs.navList.on('mouseover mouseout', 'li a', function(e) {
if (e.type == 'mouseover') {
$(this).addClass('over');
}
if (e.type == 'mouseout') {
$(this).removeClass('over');
}
});
return navHTMLobjs.navList.data('events').hasOwnProperty('mouseover')
&& navHTMLobjs.navList.data('events').hasOwnProperty('mouseout');
};
...and then use the same method in the test case like below?
test('Binding total', function() {
myNav.init();
equal(myNav.bindOver(), true, "Does the init function attach all events?");
});
What are the differences between the two?
Many thanks
Assuming other parts of the app don't need to independently verify whether the events have been subscribed to, the bindOver() should not return any value. Also, the isOverBinded() doesnt belong to the navigation module. Its existence is purely to help implement the test. In such a case, that function should be within the testing suite.
var navModule = (function(element) {
var nav = {};
var navHTMLobjs = {
navList : element,
listItems : element.find('li'),
listLinks : element.find('a')
};
nav.bindOver = function() {
navHTMLobjs.navList.on('mouseover mouseout', 'li a', function(e) {
if (e.type == 'mouseover') {
$(this).addClass('over');
}
if (e.type == 'mouseout') {
$(this).removeClass('over');
}
});
};
nav.turnOff = function() {
navHTMLobjs.navList.off('mouseover mouseout');
};
nav.init = function() {
this.bindOver();
};
return nav;
});
//var myNav = new navModule($('#nav'));
/// Test cases:
module('Navigation module');
// you might already have such a in memory object
$root = $('<ul></ul>').append('<li></li><li></li>');
var myNav = new navModule($root);
test('Binding total', function() {
myNav.init();
equal(isOverBinded(), true, "Does the init function attach all events?");
});
test('Unbinding total', function() {
myNav.turnOff();
equal(isNavTurnedOff(), true, "Does the cancel function correctly unbind events?");
});
var isNavTurnedOff = function() {
return $root.data('events').hasOwnProperty('mouseover') && $root.data('events').hasOwnProperty('mouseout');
}
var isOverBinded = function() {
return $root.data.hasOwnProperty('events') === false;
}
At the end of the day I feel, whether or not the function ought to return a value should depend on the usage of the function and not for making testing easier.
Related
I'm trying to seperate concerns using the module pattern and everything is going Ok except that I'm trying to delegate the dom strings from a module (the UIController module) to another actually I succeeded at doing it once but I don't know what is happening know it didn't work
as you see above the Domstrings object is inside the UIcontroller module so I expose it to the public so the other modules could use it
and as you see I did it before and it works fine without any problem as you see below
but when I use it inside the internalController module I got this error
so here is where I'm using it in:
so here is my code and thank you in advance:
JS
var internalController = (function(UICtrl) {
addItem: function(day, from, to, text, goingToCkecked) {
var newPlan, ID,Dom=UICtrl.getDOMstrings();
if (day === 'pick the day') {
document.querySelector(Dom.errorCase).style.visibility = "visible";
document.querySelector(".optionList").classList.add("error-red");
} else {
document.querySelector(".error-case").style.visibility = "hidden";
document.querySelector(".optionList").classList.remove("error-red");
console.log("that is me");
}
document.querySelector("#optionList").addEventListener("change", function(e) {
document.querySelector(".error-case").style.visibility = "hidden";
document.querySelector(".optionList").classList.remove("error-red");
});
})(UIController);
var UIController = (function() {
var DOMstrings = {
inputDay: ".optionList",
inputTimeF: ".inputTime",
inputTimeT: ".inputTime2",
inputText: ".inputText",
goingToCkecked: ".checkboxx",
inputBtn: ".add__btn",
planContainer: ".container",
errorCase: ".error-case",
optionList: ".optionList",
};
return {
getInput: function() {
return {
inputDay: document.querySelector(DOMstrings.inputDay).value,
inputTimeF: document.querySelector(DOMstrings.inputTimeF).value,
inputTimeT: document.querySelector(DOMstrings.inputTimeT).value,
inputText: document.querySelector(DOMstrings.inputText).value,
goingToCkecked: document.querySelector(DOMstrings.goingToCkecked).checked,
};
},
getDOMstrings: function() {
return DOMstrings;
},
}
}
};
})();
var controller = (function(interCtrl, UICtrl) {
var input, newPlan;
function setupEventListeners() {
var DOM = UICtrl.getDOMstrings();
document.querySelector(DOM.inputBtn).addEventListener("click", ctrlAddPlans);
document.addEventListener("keypress", function(e) {
if (e.keyCode === 13) {
ctrlAddPlans();
}
});
}
return {
init: function() {
console.log('the app has started');
setupEventListeners();
},
};
})(internalController, UIController);
controller.init();
// setInterval(function() {
// }, 100);
setTimeout(function() {
document.querySelector(".plansBackground").classList.add("height");
}, 1000);
I'm trying to capture if the enter key has been pressed and execute a search. This is the viewmodel for the search page.
(function ()
{
a.viewModels.userSearch = function (view, params) {
$view = $(view);
var self = a.viewModel({
users: a.collection({
url: '/admin/Account/SearchUsers',
query: {
SearchText: null
}
}).fetch(),
setPageIndex: setPageIndex,
search: search
});
$view.keypress(function (e) {
if (e.keyCode == 13) {
self.search(e);
}
});
function search(e) {
self.users.query.rowCount = 0;
self.users.query.pageIndex = 1;
self.users.fetch();
}
function setPageIndex(e) {
e.preventDefault();
self.users.query.set('pageIndex', $(e.currentTarget).data('page-index'));
self.users.fetch();
}
return self;
}
Now, this works. The problem is that it works only after pressing the 'Enter' key 2 times. Seems like I'm missing something related to the scope but js ain't my cup of tea.
If it is of any help, here goes my view model function:
function viewModel(viewModelConfig) {
var self = kendo.observable($.extend({
busy: 0,
resultMessage: null,
clearResultMessage: clearResultMessage
}, viewModelConfig));
self.bind('change', onChange);
function onChange(change) {
var errorProp, errorMsg, infoProp, infoMsg;
if (change.field.endsWith('.busy')) {
if (self.get(change.field))
self.set('busy', self.busy + 1);
else if (self.busy > 0)
self.set('busy', self.busy - 1);
}
else if (change.field.endsWith('.resultMessage')) {
var data = self.get(change.field);
self.set('resultMessage', data);
}
}
function clearResultMessage(e)
{
if (e) e.preventDefault();
self.set('resultMessage', null);
return false;
}
return self;
}
I have a similar setup on my site, and using MVVM, just add the custom enter binding within the data-bind attribute of the element to link to the function within the view-model you wish to execute.
The code to add the custom binder is as such:
kendo.data.binders.widget.enter = kendo.data.Binder.extend({
init: function(element, bindings, options) {
kendo.data.Binder.fn.init.call(this, element, bindings, options);
var binding = this.bindings.enter;
$(element.element).keyup(function(e) {
if( e.which === 13 )
bindings.get();
});
},
refresh: $.noop
});
I have implemented several jQuery plugins for my current project.
Since some plugins have functions with the same name, the one called in the last one defined.
Here is the definition of my first plugin:
$(function($)
{
$.fn.initPlugin1 = function(parameters)
{
var defaultParameters = {};
$(this).data('parameters', $.extend(defaultParameters, parameters));
return $(this);
};
$.fn.function1 = function(){ console.log('Function 1.'); };
$.fn.callFunction = function(){ $(this).function1(); };
});
And here is the definition of my second plugin:
$(function($)
{
$.fn.initPlugin2 = function(parameters)
{
var defaultParameters = {};
$(this).data('parameters', $.extend(defaultParameters, parameters));
return $(this);
};
$.fn.function2 = function(){ console.log('Function 2.'); };
$.fn.callFunction = function(){ $(this).function2(); };
});
I have also this scenario :
$("#div1").initPlugin1().callFunction();
$("#div2").initPlugin2().callFunction();
For this specific scenario the consoles shows: Function 2. Function 2.
In fact, since the callFunction() is also defined in the second plugin, this is the one used.
I would like some advise on what is the best way to solve this problem.
Is it possible to create a thing similiar to a namespace ?
Thank to #syms answer, I have created the following example.
Plugin1:
$(function($) {
$.fn.initPlugin1 = function() {
console.log('Initialized Plugin1');
return $(this);
};
$.fn.initPlugin1.testFunction = function() {
$(this).append('Function 1.');
};
});
Plugin2:
$(function($) {
$.fn.initPlugin2 = function() {
console.log('Initialized Plugin2');
return $(this);
};
$.fn.initPlugin2.testFunction = function() {
$(this).append('Function 2.');
};
});
Main:
(function($)
{
$(document).ready(
function()
{
$("#div1").initPlugin1(); //Run correctly
$("#div2").initPlugin2(); //Run correctly
$("#div1").initPlugin1.testFunction(); //Fail
$("#div2").initPlugin2.testFunction(); //Fail
});
})(jQuery);
When I run my code, I got the following error: Cannot read property 'createDocumentFragment' of null.
Apparently, the this object is corrupted.
you can try this,
$(function($) {
$.fn.initPlugin1 = function() {
console.log('Initialized Plugin1');
return $(this);
};
});
$(function($) {
$.fn.initPlugin2 = function() {
console.log('Initialized Plugin2');
return $(this);
};
$.fn.callFunction = function(param) {
$(this).append(param);
};
});
(function($) {
$(document).ready(
function() {
$("#div1").initPlugin1(); //Run correctly
$("#div2").initPlugin2(); //Run correctly
$("#div1").initPlugin1().callFunction('function1');
$("#div2").initPlugin2().callFunction('function2');
});
})(jQuery);
In order to get this ability i have extended tooltip provider.
function customTooltip($document, $tooltip) {
var tooltip = $tooltip('customTooltip', 'customTooltip', 'click'),
parentCompile = angular.copy(tooltip.compile);
tooltip.compile = function (element, attrs) {
var parentLink = parentCompile(element, attrs);
return function postLink(scope, element, attrs) {
var firstTime = true;
parentLink(scope, element, attrs);
var onDocumentClick = function () {
if (firstTime) {
firstTime = false;
} else {
element.triggerHandler('documentClick');
}
};
var bindDocumentClick = function () {
$document.on('click', onDocumentClick);
};
var unbindDocumentClick = function () {
$document.off('click', onDocumentClick);
};
scope.$watch('tt_isOpen', function (newValue) {
firstTime = true;
if (newValue) {
bindDocumentClick();
} else {
unbindDocumentClick();
}
});
scope.$on('$destroy', function onTooltipDestroy() {
unbindDocumentClick();
});
};
};
return tooltip;
}
But this approach doesn't work already because there is no tt_isOpen property in scope now. Actually i can't see any of tooltip properties just only my parent scope. I guess this happend because of changes in tooltip.js 124 line https://github.com/angular-ui/bootstrap/blob/master/src/tooltip/tooltip.js#L124. Is there any way now to close tooltip by clicking outside it or at least to get isOpen flag?
There is a pull request that implements an outsideClick trigger for tooltips and popovers. It will be included in angular-ui 1.0.0, which is expected to be released by the end of the year. Once it is implemented, you will be able to simply add tooltip-trigger="outsideClick" to your element.
There is an open pull request Here to add this feature. A hack workaround you can try is to disable then enable the trigger element as the directive will call this method:
attrs.$observe( 'disabled', function ( val ) {
if (val && ttScope.isOpen ) {
hide();
}
});
This variant works on angular 1.3.15 and angular-ui version 0.13
function customTooltip($document, $tooltip) {
var tooltip = $tooltip('customTooltip', 'customTooltip', 'click'),
parentCompile = angular.copy(tooltip.compile);
tooltip.compile = function (element, attrs) {
var parentLink = parentCompile(element, attrs);
return function postLink(scope, element, attrs) {
parentLink(scope, element, attrs);
var isOpened = false;
element.bind('click', function () {
bindDocumentClick();
});
var onDocumentClick = function () {
if (!isOpened) {
isOpened = true;
} else {
element.triggerHandler('documentClick');
unbindDocumentClick();
isOpened = false;
}
};
var bindDocumentClick = function () {
$document.on('click', onDocumentClick);
};
var unbindDocumentClick = function () {
$document.off('click', onDocumentClick);
};
scope.$on('$destroy', function onTooltipDestroy() {
unbindDocumentClick();
});
};
};
return tooltip;
}
The wierdest thing.
I have a function which calls:
var clicked_el = event.target.id;
This function breaks at this line in FireFox. It works in ie9, but not in 8 (because ie8 uses srcElement instead). Every attempt to grab the id of the element which initiated the event has been a complete flameout on my end.
I think there is a way that jQuery smoothes out browser incosistencies with jQuery.Event but I can't get anything working.
Can anyone tell me a better browser consistent method to do this?
here is the function, it's part of my backbone view.
open: function () {
iconState = true;
var self = this;
var clicked_el = event.target.id;
$('div.hr, div#footer_icons').animate({
top : '-=300'
}, 500, function () {
if (innerContentIsVisible === false) {
$('#a_close').fadeIn(500);
}
});
$('div#article').animate({
opacity : 0
}, 500,function () {
self.load_content(clicked_el);
});
},
UPDATE
The open function was being called from another function, and I forgot to pass the event paramater when I invoked the open function, which is why it was returning undefined.
here is the function which now passes the event param:
click: function(event) {
if (iconState === false) {
this.open(event);
} else {
var self = this;
var clicked_el_icon = event.target.id;
$('div#loaded_content').fadeOut(250, function() {
self.load_content(clicked_el_icon);
});
}
},
open: function (event) {
iconState = true;
alert(event);
var self = this;
var clicked_el = event.target.id;
$('div.hr, div#footer_icons').animate({
top : '-=300'
}, 500, function () {
if (innerContentIsVisible === false) {
$('#a_close').fadeIn(500);
}
});
$('div#article').animate({
opacity : 0
}, 500,function () {
self.load_content(clicked_el);
});
},
Thanks... I will not forget this lesson.
You have to declare "event" as the parameter for the "open" handler function.