Categories
Programming

Why is toggling a search box in a header so difficult in Angular 2?

In Angular 2 I have the following pseduocode…

...

As you can see I have a header with a search form container that has the class ‘visible’ when the variable ‘showSearch’ is true, and ‘invisible’ otherwise. As a component by itself with this html, this works fine. When the user clicks the search icon showSearch is set to true and the form shows.

Now the issue comes when I want to hide this container again. I want to set showSearch to false and thus hide the container when the user clicks anywhere that isn’t the header (ie. the body of the page).
Now in jQuery, this would be easy. On click of the body, you would find the element with ‘.searchContainer’ and removeClass(‘visible’).addClass(‘invisible’) right? Easy.

Except this is Angular 2. We don’t have jQuery (or want to use it) here. Normally what I would do in this case, is to have something like this in the parent component that contains the header component:




..

That’s the HTML. I pass the showSearch variable down from the parent to the child and toggle it with the parent right? That’s how normally it works. One way binding. (If the child needs to talk to the parent it uses event emitters instead).


@HostListener('click', ['$event'])
closeSearchBox(event) {
const toElement = event.toElement;
let insideHeader = false;
let node = toElement;
while (node != null && node.classList !== undefined) {
if (node.classList.contains('header-container')) {
insideHeader = true;
}
node = node.parentNode;
}

if (!insideHeader) {
const searchBox = this.elementRef.nativeElement.querySelector('.search-container');
const searchMenu = this.elementRef.nativeElement.querySelector('.header-menu-list');
if (searchBox.classList.contains('visible')) {
this.showSearch = false;
}
}
}

So this should work right? I have an HostListener on the parent (that’s really the whole body of my app) so if the user clicks anywhere it will check to see if the element is outside the header (because we dont want to close it if its inside the header), and close the search container if it is.

Problem is… it didn’t work. I don’t know why. I searched StackOverflow, Angular Docs, scoured Google etc nothing. I even tried using EventEmitter but thats usually only from child to parent and not parent to child. Two-way bindings? nope doesn’t work either. It *should* just work inherently as part of Angular’s single way binding. Maybe the HostListener is outside the Angular Zone and I can use ChangeDetectorRef to detectChanges? Nope. That didn’t work either.
I *could* use a service and just subscribe to that service from the child component … but thats kind of overkill for ONE variable change to toggle a search box.

So finally. I had to make a solution that is not so elegant, and doesn’t use any variables or bindings. But it works.

So I took a jQuery-like approach of just toggling the class manually with ‘elementRef.nativeElement.classList’ instead. Its not as pretty as using bindings, but at least this way it works.


toggleSearchHeader(showHeader) {

if (showHeader) {
this.searchHeaderMenu.nativeElement.classList.remove('visible');
this.searchHeaderMenu.nativeElement.classList.add('invisible');
this.searchHeaderBox.nativeElement.classList.remove('invisible');
this.searchHeaderBox.nativeElement.classList.add('visible');
} else {
this.searchHeaderMenu.nativeElement.classList.remove('invisible');
this.searchHeaderMenu.nativeElement.classList.add('visible');
this.searchHeaderBox.nativeElement.classList.remove('visible');
this.searchHeaderBox.nativeElement.classList.add('invisible');
}

}

I have to substitute changing one variable showSearch with a whole method that does janky DOM crap and..


@HostListener('click', ['$event'])
closeSearchBox(event) {
const toElement = event.toElement;
let insideHeader = false;
let node = toElement;
while (node != null && node.classList !== undefined) {
if (node.classList.contains('header-container')) {
insideHeader = true;
}
node = node.parentNode;
}

if (!insideHeader) {
const searchBox = this.elementRef.nativeElement.querySelector('.search-container');
const searchMenu = this.elementRef.nativeElement.querySelector('.header-menu-list');
if (searchBox.classList.contains('visible')) {
searchBox.classList.remove('visible');
searchBox.classList.add('invisible');
searchMenu.classList.remove('invisible');
searchMenu.classList.add('visible');
}
}
}

So yeah as you can see those are the changes I needed to get it to work. For some reason, using a variable inside `HostListener` and passing that down to the child component doesn’t reflect the bindings. So I have to use a more archaic way instead. Not only that but its much more difficult to test with unit tests now that it relies on the DOM instead of an Angular variable. Bleh. If anyone knows why this is, let me know please! comment on this post..

Categories
Tech

Javascript Framework/Library Reference

Base libraries (animation, dom manipulation, event handling, ajax, utility)
jQuery (this library is the most popular base library)
Zepto
Dojo
Prototype
MooTools

Utility Libraries
Underscore
Lodash
Sizzle
Modernizr (feature detection)
Yepnope (conditional loader)

MVC Frameworks
Angular
Ember
Backbone
SproutCore
ExtJS
Batman
Spine
Meteor
Exoskeleton
ActiveJS
StapesJS
JS MVC

View/Data binding frameworks
Knockout
CanJS
React
Rivets

Backbone Libraries
Thorax
Epoxy
Chaplin
Marionette
Quilt
Backpack
Rendr
Backbone Boilerplate

Templating Libraries
Handlebars
Mustache

Widget libraries (animation, ui)
jQueryUI
YUI
Bootstrap
Scriptaculous
Google Closure
ShadowboxJS
Lightbox

Mobile libraries
jQuery Mobile
Dojo Mobile
Sencha Touch

Testing Libraries
Chai
Jasmine
QUnit
Mocha
Sinon
PhantomJS
CasperJS
Istanbul
Karma

Build development Libraries
Grunt (task runner)
Bower (package manager)
RequireJS (module loader)
Yeoman (scaffolding)

Special use libraries (data-driven, graphics, maps, etc)
D3 (document manipulation)
Raphael (vector graphics)
MediaElementJS (HTML5 video/audio)
JPlayer (HTML5 video/audio)
Leaflet (maps)
ChartJS (charts)

NodeJS Libraries
Express
MongooseJS
BookshelfJS
SocketIO
Sequelize
Sails
Prerender

Categories
Programming Tech

Google Glass Unboxing and Review, Jasmine-Jquery+SquireJS example

Google Glass Review

I just got my Google Glass Explorer 2.0 edition in not that long ago, and I know alot of people are curious about how this gadget works, so I’ve put up a video of it (an unboxing one, and a review).

httpv://youtu.be/NPBNNwQB_Ss

httpv://youtu.be/RHg-bnx7r3A

Example client side Jasmine unit test using the jasmine-jquery + SquireJS libraries
Ok, so I finally figured out these client side tests. I have an example test here that uses Backbone Models as objects, the Jasmine-jquery library to test DOM interactions and the SquireJS library to mock out requireJS dependencies. Hope this helps someone.

//your jasmine test suite
describe('Car View', function() {

        beforeEach(function () {
            this.injector = new Squire();
            this.car = new Car();
        });

        afterEach(function () {
            this.injector.clean();
            this.injector.remove();
        });

       it('test car creation with a mocked out owner', function () {

            var ownerInput, brandInput, modelInput;
            var carMock = this.car;
            var injector = this.injector;

            //squire injector runs asynchronously inside its own closure so we need to run synchronously in Jasmine via run and wait
            runs(function() {
                carMock.set({
                    brand: 'Honda',
                    model: 'Civic',
                    owner: 'John Wayne',
                    url: '/car/civic/123456'
                });
                //Owner is some backbone model that is pulled in via requireJS that we want to mock because it can't be mocked normally
                var ownerMock = new Owner({ name: 'John Wayne' });
                injector.mock('dep/owner', ownerMock).require(['car.view'],function(CarView) {
                    var carView = new CarView({
                       model: carMock
                    });
                //lets say on render, your car view populates some owner field with the owner name, and the brand and model fields the same way.
                CarView.render(); 
                ownerInput = carView.$el.find('.owner input');
                brandInput = carView.$el.find('.brand input');
                modelInput = carView.$el.find('.model input');            
                });
            });

            //wait for dom to render 
            waits(1000);

            //do your assertions
            runs(function() {
                expect(brandInput[0]).toHaveValue(carMock.get('brand'));
                expect(modelInput[0]).toHaveValue(carMock.get('model'));
                expect(ownerInput[0]).toHaveValue(carMock.get('owner'));
            });

        });

});