Initialise an element with a function dynamically - jQuery - javascript

I'm currently using react router to build my site, but I have .dropdowns on several pages which I initialise using $('.dropdown').dropdown();
How can I keep initialising my dropdowns on every page I visit using react router? The onhashchange doesn't seem to register the URL changing.
This is what my dropdown function looks like.
jQuery
$(function() {
$('.dropdown').dropdown();
$(window).on('hashchange', function(e){
$('.dropdown').dropdown();
});
});
$.fn.extend({
dropdown: function(o){
var o = $.extend({ maxHeight: 600, buffer: 100, delay: 500 }, o);
return this.each(function(){
var dropdown = $(this),
toggle = dropdown.find('.toggle'),
menu = dropdown.find('.menu'),
a = dropdown.find('a'),
liheight = dropdown.height();
if(!dropdown.length){ return; }
toggle.click(function(e){
if(!menu.is(':visible')) {
$('.menu').parent().removeClass('open');
}
menu.parent().addClass('open');
});
$(document).bind('click', function(e){
if (! $(e.target).parents().hasClass('dropdown'))
menu.parent().removeClass('open');
});
});
}
});
I have also tried this, but still no luck:
$('body').on('focus', '.dropdown' function(e){
$('.dropdown').dropdown();
});
Edit: The solution
Used the componentDidMount() function in react router to initialise the dropdown every time the dropdown was rendered. This is what I added:
export const elDropdown = React.createClass({
componentDidMount: function() {
$(document).ready(function() {
$('.dropdown').dropdown();
})
},
render() {
return(
<div>...

you need to initialise the dropdowns after they become available on the page. The right place to do that is in React's lifecycle methods. In that case, componentDidMount - https://facebook.github.io/react/docs/component-specs.html#mounting-componentdidmount
You can create a component that wraps the dropdown and initialises it. Here is a simple example of how you could achieve that:
class DropdownWrapper extends React.Component {
constructor(){
super()
this.id = 'someUniqueID'
}
componentDidMount(){
$('#' + this.id).dropdown();
}
render(){
return <select id={this.id} otherProps={this.otherProps} ></select>
}
}
Notice that you will need to use unique identifiers. You can pass it to the component manually or create it randomly.

Related

Intercept event outside of Reactjs with native javascript

I want to be able to intercept an elements on click event that get's triggered by a React component and then override the functionality with native javascript.
window.addEventListener('load', function() {
var el = document.getElementById('button');
el.addEventListener('click', function(e) {
e.stopPropagation();
window.location.href = 'http://www.google.com';
});
});
The above code is placed after the bundled react code and it looks as if it can't find the element? Is there a way I can wait for React to be loaded? Or is there a better way I can handle this? Or can I override/attach an event to a react element? (I cant use react as it's already bundled)
Since your React app ends up being a JS file that you reference in your index.html file, that means if you reference a node element that's generated inside of it, jQuery can't see it because it was dynamically generated.
To solve this issue you can use two React concepts: lifecycle methods, and refs.
One, create a reference of the button you're aiming to listen globally, in the parent component of that button:
class ParentComponent extends React.Component {
render() {
return (
<button ref={(button) => this.button = button}>
Click this button
</button>
);
}
}
Two, create a componentDidMount life cycle method to make use of the ref you've just created:
class ParentComponent {
componentDidMount = () => {
this.button.addEventListener('click', function (e) {
e.preventDefault();
console.log('it works?!');
});
}
render() {
return (
<button ref={(button) => this.button = button}>
Click this button
</button>
);
}
}
Keep in mind that in other lifecycle methods like componentWillMount, or componetWillUpdate refs are either not existent or are old, because the component hasn't made it's most recent render yet. So, if you want to integrate 3rd party DOM libraries, use didMount.
I actually managed to figure it out using plain javascript, here's my code:
window.addEventListener('load', function() {
var checkExist = setInterval(function() {
var el = document.getElementById('button');
if (el != null) {
clearInterval(checkExist);
el.addEventListener('click', function(e) {
e.stopPropagation();
window.location.href = 'http://www.google.com';
});
}
}, 100);
});

Affecting other views in Marionette

I am working in Marionette and have an accordion which we have set up so that the individual panels are templates that are called in and created by
var AccorionView = require(“../folder/AccordionView”);
var expandButtons = require(“../folder/expandButtons”);
var MainPage = Marionette.View.extend({
regions: {
region: “.region”,
button: “.buttons”
},
this.newAccordion = new AccordionView({
header: “header goes here”,
childView: new panelView(),
});
this.showChildView(‘region’, this.newAccordion);”
I am going to pull in another view with the actual Expand/Collapse All button in it, which will expand and collapse all of the accordion panels on this page. The JavaScript that would be used on this page would be
expandAll: function() {
this.newAccordion.expand();
},
However, this function will be put into the new JavaScript view of the buttons. I am going to send the names of the accordion panels to the button view when calling it into this page, but how do I get the function on that view to influence the accordion panels on this main page?
I would use Backbone.Radio in this case:
const Radio = require('backbone.radio');
const accorionChannel = Radio.channel('accorion');
const MainPage = Marionette.View.extend({
// ...
initialize() {
accorionChannel.on('expand', function() {
this.newAccordion.expand();
});
accorionChannel.on('unexpand', function() {
this.newAccordion.unexpand();
});
}
// ...
});
const WhateverView = Marionette.View.extend({
someEventHandler() {
accorionChannel.trigger('expand');
// OR
accorionChannel.trigger('unexpand');
}
});
Radio channel is singleton, you can create a new one every time but it will refer to the same channel. This saves you from passing the channel variable around or having a global variable.
You can do this one of two ways
1) With triggers/childViewEvents
// in expandButtons
expandButtons = Marionette.View.extend(
triggers: {
'click #ui.expandAll': 'expandAll'
}
);
// in MainPage
MainPage = Marionette.View.extend({
childViewEvents: {
'expandAll': 'expandAll'
},
expandAll: function(child) {
this.newAccordion.expand();
// OR
this.getChildView('region').expand();
}
})
OR
2) With Backbone.Radio
// in expandButtons
var Radio = require('Backbone.Radio');
var expandChannel = Radio.channel('expand');
var expandButtons = Marionette.View.extend({
events: {
'click #ui.expandAll': 'expandAll'
},
expandAll: function(e) {
expandChannel.trigger('expand:all');
}
});
// in AccordionView
var AccordionView = Marionette.View.extend({
channelName: 'expand',
radioEvents: {
'expand:all': 'expand' // triggers this.expand();
}
});
In this case, it might be even easier to do #2 but instead of adding the radio listener to the AccordionView, attach the listeners to the PanelView (AccordionView's childView). This is because AccordionView's expand function will likely have to iterate each of its children like:
this.children.each(function(childView) {
childView.expand();
});

ReactJS remove component

I'm trying to learn the basics of facebook's react.js library and I've been lately thinking about a few stuff I can do with it just to get used to the way it works . I'm trying to make a div that contains 2 buttons one is OPEN and the other is CLOSE, when you click the OPEN the react will render a div containing anything (Like a msg saying "You clicked"), this is fine up to now but I cannot figure out how to make it disappear once clicked on the CLOSE button, does anyone know how to do that ? Thanks ^^
There are at least four ways, that depends on the real problem you need to solve:
1) Add #your-div-id.hidden { display:none } styles and add/remove hidden class on click (maybe not React way)
2) Change view state (i.e. opened flag). That's a React way and maybe the simplest choice
onOpen() {
this.setState({ opened: true });
}
onClose() {
this.setState({ opened: false });
}
render() {
var div = (this.state.opened) ? (<div>Your Div Content</div>) : undefined;
return (
//some your view content besides div
{div}
);
}
3) If you use Flux. Move state to Store and subscribe to changes. That maybe useful if you gonna show your div at many parts of your app (i.e. implement error popups which may be shown at any part of an application).
So, first of all let's keep warnings at the store:
var CHANGE_EVENT = 'change';
const warnings = [];
var WarningsStore = assign({}, EventEmitter.prototype, {
getWarnings: () => return warnings,
emitChange: () => this.emit(CHANGE_EVENT),
addChangeListener: callback => this.on(CHANGE_EVENT, callback),
removeChangeListener: callback => this.removeListener(CHANGE_EVENT, callback)
});
WarningsStore.dispatchToken = AppDispatcher.register(action => {
switch(action.type) {
case ActionTypes.ADD_WARNING:
warnings.push(action.warning);
WarningsStore.emitChange();
break;
case ActionTypes.DISMISS_WARNING:
_.remove(warnings, {type: action.warningType}); //that's lodash or underscore method
WarningsStore.emitChange();
break;
}
});
After we have a warnings store, you may subscribe to it from YourView and show popup on each AppDispatcher.dispatch({type: ADD_WARNING, warningType: 'Error'});
var YourView = React.createClass({
componentDidMount: function() {
WarningsStore.addChangeListener(this._onChange);
},
componentWillUnmount: function() {
WarningsStore.removeChangeListener(this._onChange);
},
_onChange() {
this.setState({ warnings: WarningsStore.getWarnings() });
},
render() {
//if we have warnigns - show popup
const warnings = this.state.warnings,
onCloseCallback = () => AppDispatcher.dispatch({type: DISSMISS_WARNING, warningType: warnings[0].warningType});
popup = (warnings.length) ? <YourPopupComponent warning={warnings[0]} onClose={onCloseCallback}> : undefined;
return (
//here the main content of your view
{popup}
);
}
})
4) If you simplified your example, but actually instead of div you need to show/hide another page - you should use react-router
Here's how I would do this:
var foo = React.CreateClass({
getInitialState: function() {
return {
showDiv: false, //Or true if you need it displayed immediately on open
}
},
showIt: function() {
this.setState({showDiv: true});
},
hideIt: function() {
this.setState({showDiv: false});
},
render: function() {
return (<div style={{display: this.state.showDiv ? 'block' : 'none'}}>Your content here</div>);
}
});
What this will do is on state change, the style block of the div will be re-evaluated. If the showDiv state variable is true, it'll display as a block element. Otherwise it'll display none. You could, in theory, do this with CSS as well.
Here's a jsFiddle showing this being done with both CSS classes AND the style attribute on the div element.

Attach component to dynamically created elements with Twitter Flight

Been looking to figure out how with Twitter Flight can attach to dynamic created elements.
Having the following HTML
<article>Add element</article>
And the following component definition
var Article = flight.component(function () {
this.addElement = function () {
this.$node.parent().append('<article>Add element</article>');
};
this.after('initialize', function () {
this.on('click', this.addElement);
});
});
Article.attachTo('article');
Once a new element is created, the click event doesn't fire. Here's the fiddle: http://jsfiddle.net/smxx5/
That's not how you should be using Flight imho.
Each component should be isolated from the rest of the application, therefore you should avoid this.$node.parent()
On the other hand you can interact with descendants.
My suggestion is to create an "Articles manager" component that uses event delegation.
eg.
http://jsfiddle.net/kd75v/
<div class="js-articles">
<article class="js-article-add">Add element</article>
<div/>
and
var ArticlesManager = flight.component(function () {
this.defaultAttrs({
addSelector: '.js-article-add',
articleTpl: '<article class="js-article-add">Add element</article>'
});
this.addArticle = function () {
this.$node.append(this.attr.articleTpl);
};
this.after('initialize', function () {
this.on('click', {
addSelector: this.addArticle
});
});
});
ArticlesManager.attachTo('.js-articles');
Try attaching Article to each new article added:
JSFiddle: http://jsfiddle.net/TrueBlueAussie/smxx5/2/
var Article = flight.component(function () {
this.addElement = function () {
var newArticle = $('<article>Add element</article>');
this.$node.parent().append(newArticle);
Article.attachTo(newArticle);
};
this.after('initialize', function () {
this.on('click', this.addElement);
});
});
Article.attachTo('article');
The Article.attachTo('article'); at the end, that runs once on load, will only attach to existing article elements.
I hit this problem, and worked around is as follows...
Javascript: All thrown together for brevity, but could easily be separated.
(function(){
var TestComponent, LoaderComponent;
TestComponent = flight.component(function() {
this.doSomething = function()
{
console.log('hi there...');
};
this.after('initialize', function() {
this.on('mouseover', this.doSomething);
});
});
LoaderComponent = flight.component(function() {
this.attachComponents = function()
{
TestComponent.attachTo('.test');
};
this.after('initialize', function() {
// Initalise existing components
this.attachComponents();
// New item created, so re-attach components
this.on('newItem:testComponent', this.attachComponents);
});
});
LoaderComponent.attachTo('body');
}());
HTML: Note that one .test node exists. This will be picked up by Flight on initialization (i.e. not dynamic). We then add a second .test node using jQuery and fire off the event that the LoaderComponent is listening on.
<div class="test">
<p>Some sample text.</p>
</div>
<script>
$('body').append('<div class="test"><p>Some other text</p></div>').trigger('newItem:testComponent');
</script>
This is obviously a very contrived example, but should show that it's possible to use Flight with dynamically created elements.
Hope that helped :)

Making a template helper reactive in Meteor

I am building a chat application and on my "new chats" page I have a list of contacts, which you can select one by one by tapping them (upon which I apply a CSS selected class and push the user id into an array called 'newChatters'.
I want to make this array available to a helper method so I can display a reactive list of names, with all users who have been added to the chat.
The template that I want to display the reactive list in:
<template name="newChatDetails">
<div class="contactHeader">
<h2 class="newChatHeader">{{newChatters}}</h2>
</div>
</template>
The click contactItem event triggered whenever a contact is selected:
Template.contactsLayout.events({
'click #contactItem': function (e) {
e.preventDefault();
$(e.target).toggleClass('selected');
newChatters.push(this.username);
...
The newChatters array is getting updated correctly so up to this point all is working fine. Now I need to make {{newChatters}} update reactively. Here's what I've tried but it's not right and isn't working:
Template.newChatDetails.helpers({
newChatters: function() {
return newChatters;
}
});
How and where do I use Deps.autorun() to make this work? Do I even need it, as I thought that helper methods auto update on invalidation anyway?
1) Define Tracker.Dependency in the same place where you define your object:
var newChatters = [];
var newChattersDep = new Tracker.Dependency();
2) Use depend() before you read from the object:
Template.newChatDetails.newChatters = function() {
newChattersDep.depend();
return newChatters;
};
3) Use changed() after you write:
Template.contactsLayout.events({
'click #contactItem': function(e, t) {
...
newChatters.push(...);
newChattersDep.changed();
},
});
You should use the Session object for this.
Template.contactsLayout.events({
'click #contactItem': function (e) {
//...
newChatters.push(this.username);
Session.set('newChatters', newChatters);
}
});
and then
Template.newChatDetails.helpers({
newChatters: function() {
return Session.get('newChatters');
}
});
You could use a local Meteor.Collection cursor as a reactive data source:
var NewChatters = new Meteor.Collection("null");
Template:
<template name="newChatDetails">
<ul>
{{#each newChatters}}
<li>{{username}}</li>
{{/each}}
</ul>
</template>
Event:
Template.contactsLayout.events({
'click #contactItem': function (e) {
NewChatters.insert({username: this.username});
}
});
Helper:
Template.newChatDetails.helpers({
newChatters: function() { return NewChatters.find(); }
});
To mimick the behaviour of Session without polluting the Session, use a ReactiveVar:
Template.contactsLayout.created = function() {
this.data.newChatters = new ReactiveVar([]);
}
Template.contactsLayout.events({
'click #contactItem': function (event, template) {
...
template.data.newChatters.set(
template.data.newChatters.get().push(this.username)
);
...
Then, in the inner template, use the parent reactive data source:
Template.newChatDetails.helpers({
newChatters: function() {
return Template.parentData(1).newChatters.get();
}
});
for people who is looking for a workaround for this in the year 2015+ (since the post is of 2014).
I'm implementing a posts wizard pw_module where I need to update data reactively depending on the route parameters:
Router.route('/new-post/:pw_module', function(){
var pwModule = this.params.pw_module;
this.render('post_new', {
data: function(){
switch (true) {
case (pwModule == 'basic-info'):
return {
title: 'Basic info'
};
break;
case (pwModule == 'itinerary'):
return {
title: 'Itinerary'
};
break;
default:
}
}
});
}, {
name: 'post.new'
});
Later in the template just do a:
<h1>{{title}}</h1>
Changing routes
The navigation that updates the URL looks like this:
<nav>
Basic info
Itinerary
</nav>
Hope it still helps someone.

Categories

Resources