How to make react.js play nice together with zurb reveal modal form - zurb-foundation

I am trying to integrate zurb reveal with form into react component. So far next code properly displays modal form:
ModalForm = React.createClass({
handleSubmit: function(attrs) {
this.props.onSubmit(attrs);
return false;
},
render: function(){
return(
<div>
Add new
<div id="formModal" className="reveal-modal" data-reveal>
<h4>Add something new</h4>
<Form onSubmit={this.handleSubmit} />
<a className="close-reveal-modal">×</a>
</div>
</div>
);
}
});
The Form component is pretty standard:
Form = React.createClass({
handleSubmit: function() {
var body = this.refs.body.getDOMNode().value.trim();
if (!body) {
return false;
}
this.props.onSubmit({body: body});
this.refs.body.getDOMNode().value = '';
return false;
},
render: function(){
return(
<form onSubmit={this.handleSubmit}>
<textarea name="body" placeholder="Say something..." ref="body" />
<input type="submit" value="Send" className="button" />
</form>
);
}
});
Problem: When I render form component within modal form component and enter something into form input then I see in console exception Uncaught object. This is a stack:
Uncaught object
invariant
ReactMount.findComponentRoot
ReactMount.findReactNodeByID
getNode
...
If I just render form component directly in the parent component then everything works. Could anybody help please?

In short, you're doing this wrong and this is not a bug in react.
If you use any kind of plugin that modifies the react component's dom nodes then it's going to break things in one way or another.
What you should be doing instead is using react itself, and complementary css, to position the component in the way you'd like for your modal dialog.
I would suggest creating a component that uses react's statics component property to define a couple of functions wrapping renderComponent to give you a nice clean function call to show or hide a react dialog. Here's a cut down example of something I've used in the past. NB: It does use jQuery but you could replace the jQ with standard js api calls to things like elementById and etc if you don't want the jQuery code.
window.MyDialog = React.createClass({
propTypes: {
title: React.PropTypes.string.isRequired,
content: React.PropTypes.string.isRequired
},
statics: {
// open a dialog with props object as props
open: function(props) {
var $anchor = $('#dialog-anchor');
if (!$anchor.length) {
$anchor = $('<div></div>')
.prop('id', 'dialog-anchor');
.appendTo('body');
}
return React.renderComponent(
MyDialog(props),
$anchor.get(0)
);
},
// close a dialog
close: function() {
React.unmountComponentAtNode($('#dialog-anchor').get(0));
}
},
// when dialog opens, add a keyup event handler to body
componentDidMount: function() {
$('body').on('keyup.myDialog', this.globalKeyupHandler);
},
// when dialog closes, clean up the bound keyup event handler on body
componentWillUnmount: function() {
$('body').off('keyup.myDialog');
},
// handles keyup events on body
globalKeyupHandler: function(e) {
if (e.keyCode == 27) { // ESC key
// close the dialog
this.statics.close();
}
},
// Extremely basic dialog dom layout - use your own
render: function() {
<div className="dialog">
<div className="title-bar">
<div className="title">{this.props.title}</div>
<a href="#" className="close" onClick={this.closeHandler}>
</div>
</div>
<div className="content">
{this.props.content}
</div>
</div>
}
});
You then open a dialog by calling:
MyDialog.open({title: 'Dialog Title', content: 'My dialog content'});
And close it with
MyDialog.close()
The dialog always attaches to a new dom node directly under body with id 'dialog-anchor'. If you open a dialog when one is already open, it will simply update the dom based on new props (or not if they're the same).
Of course passing the content of the dialog as a props argument isn't particularly useful. I usually extend below to either parse markdown -> html for the content or get some html via an ajax request inside the component when supplying a url as a prop instead.
I know the above code isn't exactly what you were looking for but I don't think there's a good way to make a dom-modifying plugin work with react. You can never assume that the dom representation of the react component is static and therefore it can't be manipulated by a 3rd party plugin successfully. I honestly think if you want to use react in this way you should re-evaluate why you're using the framework.
That said, I think the code above is a great starting point for a dialog in which all manipulation occurs inside the component, which afterall is what reactjs is all about!
NB: code was written very quickly from memory and not actually tested in it's current form so sorry if there are some minor syntax errors or something.

Here is how to do what Mike did, but using a zf reveal modal:
var Dialog = React.createClass({
statics: {
open: function(){
this.$dialog = $('#my-dialog');
if (!this.$dialog.length) {
this.$dialog = $('<div id="my-dialog" class="reveal-modal" data-reveal role="dialog"></div>')
.appendTo('body');
}
this.$dialog.foundation('reveal', 'open');
return React.render(
<Dialog close={this.close.bind(this)}/>,
this.$dialog[0]
);
},
close: function(){
if(!this.$dialog || !this.$dialog.length) {
return;
}
React.unmountComponentAtNode(this.$dialog[0]);
this.$dialog.foundation('reveal', 'close');
},
},
render : function() {
return (
<div>
<h1>This gets rendered into the modal</h1>
<a href="#" className="button" onClick={this.props.close}>Close</a>
</div>
);
}
});

Related

Ember extend input helper so that I can add a class whenever the html5 invalid event fires

In a non Ember world I could put this in my document ready:
$("input").on("invalid", function(event) {
$(this).addClass('isDirty');
});
And then I would know that whenever a form is submitted, it would inturn fire the invalid event on invalid inputs and allow me to mark them as dirty for css purposes. I tried to do this in Ember in a component (in didInsertElement):
export default Ember.Component.extend({
didInsertElement: function() {
this.$('input, textarea').on('invalid', function() {
console.log('worked!');
Ember.$(this).addClass('isDirty');
});
},
actions: {
keyDownAction: function(value, event) {
// Mark input/textarea as dirty
Ember.run(() => {
this.$('input, textarea').addClass('isDirty');
})
if (this.get('keyDown')) {
this.sendAction('keyDown', value, event);
}
},
focusInAction: function(value, event) {
if (this.get('focusIn')) {
this.sendAction('focusIn', value, event);
}
},
focusOutAction: function(value, event) {
// Mark input/textarea as dirty
Ember.run(() => {
this.$('input, textarea').addClass('isDirty');
})
if (this.get('focusOut')) {
this.sendAction('focusOut', value, event);
}
}
}
})
hbs:
{{input type=type value=value id=inputId class=inputClass name=name tabindex=tabindex autofocus=autofocus required=required list=list
min=min max=max step=step
key-down="keyDownAction" focus-in="focusInAction" focus-out="focusOutAction"}}
<span class="bar"></span>
<label class="{{if value 'text-present'}}">{{placeholder}}</label>
But my event isn't firing. Can I attach the ember input helper to the html5 invalid event?
It works for me. You just need to make sure you wrap both component and submit button (for example, <button type='submit'>Submit</button>) in <form> element.
For example, template:
<form>
{{my-component type='text' required='true' placeholder='My field'}}
<button type='submit'>Submit</button>
</form>
Clicking submit button without <form> will do nothing. Clicking submit button when both elements are inside <form> will log worked! in console, add class isDirty to <input> and display native browser dialog to fill this field.
Working demo.
Full code behind demo.
It looks like you're doing things way too complicated. from what i understand is you're marking a textarea's validty depending on its value.
Lets say you have a text area
{{textarea value=textValue class="form-control"}}
And this input is supposed to have value for validity.
textAreIsValid: function() {
return !Ember.isEmpty(this.get('textValue'); // meaning the text can't be empty.
}.property('textValue')
To show its validity you to user, i would wrap the text area like this
<div class="control-group {{if textAreIsValid 'valid-class' 'has-error'}}">
XX text area XX
</div>
Also instead of keyPres and up you could be observing the value
areaValueChanged: function() {
}.observes('textValue'),
It turns out that adding an Ember action to the submit button, such as:
<button type="submit" {{action 'testAction'}}>Go</button>
And returning false from the action, was not enough.
I did try adding preventDefault=false to the button, which worked in that the invalid event fired, but didnt work in that the whole page then submited rather than Ember handling things
The best solution therefore was to call this.$('form')[0].checkValidity(); just before returning false in the action, i.e.
if (formIsInvalid) {
this.$('form')[0].checkValidity();
return false;
}
Example twiddle now working: https://ember-twiddle.com/13af9d78ff5007626960

Ember Dynamically Generated HTML

I have a requirement where I need to add html after the DOM has been rendered.
I was wondering if it is possible to manipulate the DOM after creation and dynamically add html and also specifying an associated ember action.
E.g. The intension of what I want to achieve:
$('.add').on("click", function(e){
e.preventDefault();
i += 1;
var content = "<div class=\"item dodgerBlue\"><h1>"+i+"</h1></div>";
var content + "{{action "owlItemClicked" titleModel.id titleModel.index titleModel on="click" }}"
owl.data('owlCarousel').addItem(content);
});
Specifically I want to add another Item to my carousel:
http://owlgraphic.com/owlcarousel/demos/manipulations.html
I'm not sure that the triple-stash, which just includes content without escaping it, will work with an {{action}}. In any case, it looks to me that you'd be better off simply defining the html within an each block and letting Ember handle the content addition.
{{#each model as |titleModel index|}}
<div class=\"item dodgerBlue\">
<h1>{{index}}</h1>
</div>
{{action "owlItemClicked" titleModel.id titleModel.index titleModel on="click" }}
{{/each}}
I noticed you have a titleModel.index property, so maybe you don't need the index in each block and can use the model's property instead.
That would be the Ember way to do it. However, it looks like this OwlCarousel widget wants to have the html passed directly. But there's also a reinit method, so maybe that would be sufficient to tell it that new content has been added through Ember. Something like the following:
carouselOptions: {
// ...
},
didInsertElement: function() {
Ember.run.scheduleOnce('afterRender', this, function() {
var $c = Ember.$('#the-carousel');
if ($c.length) {
$c.owlCarousel( this.get('carouselOptions') );
}
});
},
actions: {
addCarouselItem: function(e){
e.preventDefault();
// Add a new item to your array.
// Not shown because i have no idea of your code.
// Ember will handle DOM insert.
// Then reinit carousel.
$("#the-carousel").data('owlCarousel').reinit( this.get('carouselOptions') );
},
owlItemClicked: function(e) {
// ...
}
}
You can insert dynamically created HTML using the triple-stache in your template.
// controller.js
dynamicHtml: Ember.computed({
get() {
return `<div>Hello World!</div>`;
}
})
...
{{! template.hbs }}
{{{dynamicHtml}}}

ember autofocus component after insertion into DOM

I want to display an input field, and immediately autofocus it upon clicking a button. Im still new to Ember so i am not sure this is the correct approach, but I tried to wrap as an ember component
template
{{#if showCalendarForm}}
{{new-calendar focus-out='hideNewCalendar' insert-newline='createCalendar'}}
{{else}}
<button class="btn btn-sm btn-primary" {{action "showNewCalendar"}}>New</button>
{{/if}}
new-calendar component handlebars:
<div class="input-group">
{{input
class = 'form-control'
id = 'newCalendar'
type = 'text'
placeholder = 'New calendar'
value = calendarName
action = 'createCalendar'
}}
</div>
new-calendar component js
import Ember from 'ember';
export default Ember.Component.extend({
didInsertElement: function() {
this.$().focus();
}
});
When I click the button, the text field is displayed, but autofocus and hitting enter doesnt work
The way the jQuery is written, you are trying to set focus on the <div class="input-group">, try this instead:
didInsertElement: function() {
this.$('input').focus();
}
Another way to do this would be to extend the Ember.TextField:
export default Ember.TextField.extend({
becomeFocused: function() {
this.$().focus();
}.on('didInsertElement')
});
Then, in your new-calendar template, use this component:
{{focus-input
class = 'form-control'
id = 'newCalendar'
type = 'text'
placeholder = 'New calendar'
value = calendarName
action = 'createCalendar'
}}
This way you can reuse the focus-input component wherever you need to.
As for hitting enter to create the calendar, I think you want to listen for the keyPress event, check to see if it's the enter key, and then send the action rather than trying to use insert-newline='createCalendar'.
//in FocusInputComponent
keyPress: function(e) {
// Return key.
if (e.keyCode === 13) {
this.sendAction();
}
}
Try wrapping your focus call in an Ember.run and schedule it to be run in the after render queue like this:
didInsertElement: function()
{
Ember.run.scheduleOnce('afterRender', this, function() {
this.$().focus();
});
}
this blog post has helped me a lot in understanding ember's lifecycle hooks:
http://madhatted.com/2013/6/8/lifecycle-hooks-in-ember-js-views

Ember.js - Animate Handlebars Change

I am using an {{if}} statement in a handlebars template (in Ember.js) and currently have it setup so if foo is true, a form is displayed.
I have a button which toggles the value of foo and hides/shows the form. This is done with an {{action}} attribute.
What I would like to do is animate this change. If possible, how can I do this using the current setup?
Note: I would be fine with fadeIn/fadeOut (jQuery).
Take a look at Liquid Fire (see: https://github.com/ef4/liquid-fire). I personally didn't work with it yet, but the examples look promising. Especially this form example looks similar to what you want: http://ef4.github.io/ember-animation-demo/#/animated-if-demo
Source is on the next slide: http://ef4.github.io/ember-animation-demo/#/animated-if-source
You can do this way:
in the handlebars file:
<script type="text/x-handlebars" data-template-name="newPost">
<p> Template for New Post </p>
<div class="errorMsg">
<p>Error Message</p>
</div>
</script>
in the view you can set a trigger with an action, and hide the div with the message error
App.NewPostView = Ember.View.extend({
showError: function() {
$('.errorMsg').fadeToggle("slow");
},
didInsertElement : function(){
$('.errorMsg').hide();
this.get('controller').on('showError', this, this.showError);
},
willClearRender: function()
{
this.get('controller').off('showError', this, this.showError);
}
});
And finally in the controller, this must extend to Ember.Evented and when you want to show the message error, you must call a this.trigger error.
App.NewPostController = Ember.ObjectController.extend(Ember.Evented,{
actions:{
// This an example the method that verify and show error after validating
addNewPost: function(post){
if(post.get('name') === 'valid'){
//do your business
}else{
//this will show the message error
this.trigger('showError');
}
}
}
})
when you want to hide the message error, you must call this.trigger('showError') again and this hide the message, you can use this with other effects.

Ember removes element from DOM after creating it, and jQuery can not find it

I have this wrapper around Ember.Select, to activate Select2 features:
App.Select2SelectView = Ember.Select.extend({
prompt: 'Please select...',
classNames: ['input-xlarge'],
didInsertElement: function() {
Ember.run.scheduleOnce('afterRender', this, 'processChildElements');
},
processChildElements: function() {
this.$().select2({
// do here any configuration of the
// select2 component
escapeMarkup: function (m) { return m; } // we do not want to escape markup since we are displaying html in results
});
},
willDestroyElement: function () {
this.$().select2('destroy');
}
});
Sometimes I need to make a drop-down invisible, and I do it like this:
{{#if cityVisible}}
<div class="control-group">
<label class="control-label">City</label>
<div class="controls">
{{view SettingsApp.Select2SelectView
id="city-id"
contentBinding="currentCities"
optionValuePath="content.city"
optionLabelPath="content.city"
selectionBinding="controller.selectedCity"
prompt="Select a city"}}
<i class="help-block">Select the city for your geographical number</i>
</div>
</div>
{{/if}}
But whenever the drop-down is invisible, I get the following error:
Uncaught TypeError: Cannot call method 'select2' of undefined
I guess the element is inserted, but then removed by Ember from the DOM (bound property cityVisible), so that jQuery is not able to find it?
What can I do to avoid that error message? I do not want to make the view visible/invisible, I want to keep the whole control-group under the cityVisible control.
This is normal behaviuor that ember removes the view, as a workaround you could do the following:
HTML
<div {{bindAttr class="view.cityVisible::hideCities"}}>
<div class="control-group">
...
</div>
</div>
CSS
.hideCities {
display: none;
}
Remove the {{#if}} around the html block, and wrap it with a div instead on which you set a css class which contains display: none; you could use the cityVisible or a different property in your view or controller and set it to true/false to toggle it's visibility. This mecanisnm should leave your html markup in the DOM an thus available for jQuery. Note that if your citiesVisible property lives in your controller then remove the view. prefix from view.citiesVisible to be only citiesVisible, this depends on your setup.
See demo here.
Hope it helps.