How to trigger a callback in a Polymer component when it's been attached to the DOM? - polymer-1.0

I having troules integrating jsPlumb in a Polymer component that is intended to be used as a plugin in an Electron wrapped application. The version of Polymer I'm using is 1.2.0 because that is what was shipped with the the application.
jsPlumb require that the dragable elements and their sources and targets have been mounted to the DOM but that doesn't seem to be the case when attached is fired.
What I did was to set a simeout to keep querying the DOM until the componet's been "truly" mounted, then continue with the initialization passing the new instance of jsPlumb to the nodes in the graph so they can properly set up the sources and targets for each input and output port.
Question
What I want to know is whether there is another way to "wait" for the component to be available for jsPlumb using only the Polymer API instead of a timeout?
<link rel="import" href="my-node.html">
<dom-module id="my-graph">
<template>
<div id="canvas">
<template is="dom-repeat" items="{{nodes}}">
<my-node></my-node>
</template>
</div>
</template>
<script>
Polymer({
is: "my-graph",
properties: {
nodes: {
type: Array,
value: function(){return [];}
}
},
attached: function() {
this._attachedDeferred();
},
_attachedDeferred: function() {
// wait for the component to be attached
if (!document.contains(this) || !this.offsetParent) {
setTimeout(function(){
this._attachedDeferred();
}.bind(this), 100);
return;
}
// ready to init jsPlumb
this.instance = jsPlumb.getInstance({
Container: "canvas"
});
// init the graph nodes
var nodes = [
{
pos: [0,0],
jsPlumb: this.instance; // the new instance of jsPlumb
inputs: {...},
outputs: {...}
}
];
this.nodes = nodes;
}
});
</script>
</dom-module>

I assume you're trying to access the <my-node> elements?
<template is="dom-repeat"> creates it's contents asynchronously, so it's children won't be yet available during attached.
You need to listen for the dom-change event before you can access them.
Check out the documentation after the "Initialization timing for local DOM children" header.

Related

ReactJS component rendering and how to append elements to the div the component is mounted on

I'm having this use case where there is a 'load more' button on the page to fetch more items from an API endpoint, and so I want these items to get appended to the DOM when they arrive. The thing is that the HTML page that is served by the web server comes with some extra list items (as seen below) within the same item-list div container I mount my React component, with empty data/props, on page load.
<div class="item-list">
<div class="item-list__child-item"></div>
<div class="item-list__child-item"></div>
...
<div class="item-list__child-item"></div>
</div>
My assumption is that if I handle this in the ReactJS way, as soon as I fetch more items from the server (REST) and append those items to an 'itemList' state array, react will somehow replace all of the content that holds that 'item-list' div where the component was mounted on.
A quick workaround that I'm thinking would work and that it doesn't rely on the isomorphic stuff and pre-rendering the react component on the server, is to create a separate sibling div having the same div class name 'item-list' and adding an id attribute to mount the component on, so the resulting HTML would go like:
<div class="item-list">
<div class="item-list__child-item"></div>
<div class="item-list__child-item"></div>
...
<div class="item-list__child-item"></div>
</div>
<div class="item-list" id="react-component-name"></div>
Maybe there is a cleaner way to do it without getting into the isomorphic stuff, or maybe I'm not understanding the React concept and how it works. Anyways will appreciate any directions you may have on this.
OK, your question wasn't clear on this, but the data that is represented by what was generated in the HTML will be entirely different from the data that you will be getting via AJAX.
There's a simple solution to this. Instead of creating an entirely new DOM element that will be adjacent to your original DOM layout, what you will do is grab the data that was already there, store it into an array, and append the new data that you will grab via AJAX into that Array. This way, you will reap the benefit of React's DOM diffing. Why is it useful? Maybe you want to let the user sort the data, or interact with the data directly, while it will remain in full control of a parent React component.
So anyways, take a look at this fiddle: https://jsfiddle.net/x4jjry04/3/. It's based on Paul Booblic's fiddle.
var Page = React.createClass({
getDefaultProps: function () {
return {
items: []
}
},
getInitialState : function(){
return{
items : this.props.items
}
},
componentDidMount: function () {
// Mimics an AJAX call, but replace this with an actial AJAX call.
setTimeout(function () {
var dataFromAjax = ['one', 'two', 'three'];
this.setState({
items: this.state.items.concat(dataFromAjax)
});
}.bind(this));
},
addClick : function(){
this.state.items.push("more");
this.forceUpdate();
},
render : function(){
return <div>{this.state.items.map(function(item){return <div className="bla-bla-class">{item}</div>})}<br/><div onClick={this.addClick}>ADD</div></div>;
}
});
var elements = document.querySelectorAll('.item-list__child-item');
var initialvalues = Array.prototype.slice
.call(elements)
.map(function (div) {
return div.innerHTML;
});
React.render(<Page items={initialvalues} />, document.body);
Check this simple fiddle demo:
https://jsfiddle.net/x4jjry04
var Page = React.createClass({
getInitialState : function(){
return{
items : ["one","two","three"]
}
},
addClick : function(){
this.state.items.push("more");
this.forceUpdate();
},
render : function(){
return <div>{this.state.items.map(function(item){return <div className="bla-bla-class">{item}</div>})}<br/><div onClick={this.addClick}>ADD</div></div>;
}
});
React.render(<Page />, document.body);
I assume you are using react serverside to render the list?
On page load you fetch the original list from the server and have the component "re-render" the elements. re-render is in quotes, because React wont actually update the list unless the list changes. Now you are setup with a component that works as expected, and you can add elements to the list as you want.
The general Idea with isomorphic/universal React is that you treat your app as a normal Single Page App, and let React handle the magic of dirty checking.
This also means that you can use the same component when rendering on the server, since your component doesn't contain any client specific code.

REACT.js and render but using innerHTML of element rendering into?

I have a need and I can't seem to overcome it.
Because my page uses both templating rendering AND react... I have this div that has an id, lets say 'foo'.
Now, I'd like to take the contents of "foo", THEN render my component with the contents of foo. My React component needs to have a certain look, but the page template has another.... does that make sense?
ie.
page renders with something like:
<div id="foo">
<ul>
<li>Item One</li>
<li>Item two</li>
</ul>
</div>
then in my jsx file, I have:
var fooElem = document.getElementById('foo');
var contents = fooElem && fooElem.innerHTML || '';
if(fooElem) {
React.render(
<MyCOMPONENT data={contents} />,
fooElem
);
}
I figure it would be an easy thing to just get the innerHTML of the div and then render a component in it with 'new' component content surrounding the 'innerhtml' that I grabbed. As you can see, I take a prop of "data" and I use that within my component to spit it out again, but in a different format. etc... that "foo" div is handed back by the backend, so I can't alter it other than "trying to get the innerHTML, manipulate it, and render a component into that space..".
Any help would be appreciated.
I get errors that the element has been modified and such.. and seems to just go in some kind of loop.
You could do something along the lines of:
'use strict';
var React = require('react');
var myComponent = React.createClass({
getInitialState: function() {
return {
content: ''
};
},
// Learn more about React lifecycle methods: https://facebook.github.io/react/docs/component-specs.html
componentWillMount: function() {
// Get the content of foo
var content = document.getElementById('foo').innerHTML;
// Remove the old templated foo from the page
// This assumes foo is a direct child of document.body
document.body.removeChild(document.getElementById('foo'));
this.setState({
content: content
});
},
render: function() {
<div id={'foo'}>
{this.state.content}
</div>
}
});
// This assumes you want to render your component directly into document.body
React.render(myComponent, document.body);
This will:
Read the contents of foo into the React component's state.
Remove the foo element from the page.
Render the new React component (with the content of foo) into the page.

how to get clean version of the html in angularjs

I wrote an application that uses angularjs for data-binding.
The application is some sort of visual html builder.
When the user is done, I want to allow him to export the HTML.
Since I did massive usage in angularjs, its attributes are all over the place, and the generated HTML is ugly.
Is there anyway to get clean version of the HTML?
this example will export the ugly html: http://jsfiddle.net/ga25hep2/
<div ng-app="myApp">
<div id="the_html" ng-controller="MyCtrl">
Hello, {{name}}!
</div>
<button onclick='exportMe()'>export</button>
</div>
var myApp = angular.module('myApp',[]);
function MyCtrl($scope) {
$scope.name = 'Superhero';
}
function exportMe(){
alert(document.getElementById('the_html').outerHTML)
}
If you can update angular to a version after 1.3 (you really should, there are lots of other benefits, too), then you can disable all of the ng- class spam by disabling debug info in your app:
myApp.config(function ($compileProvider) {
$compileProvider.debugInfoEnabled(false);
});
You can see the result in an updated fiddle here.
Note that I changed the structure of things slightly but the functionality is the same.
I don't think it exists a native way to do this. AngularJS is not about such kind of work.
Just write a script that parses your exported code and remove each ng-* attribute
We need to iterate through all the dom nodes and remove the ng related attributes and ng related classes.
function exportMe(){
var targetEl = document.getElementById('the_html');
var nodes = targetEl.querySelectorAll('*');
nodes = Array.prototype.slice.call(nodes); // convert NodeList to array
nodes.unshift(targetEl); // include the root node while iterating.
nodes.forEach(function (node) {
Array.prototype.slice.call(node.attributes).forEach(function (attr) {
var classes = Array.prototype.slice.call(node.classList); // conver classList to array
classes = classes.filter(function (cls) {
return !(cls.indexOf('ng-') === 0 || cls.indexOf('data-ng-') === 0);
});
// remove ng related classes
node.setAttribute('class', classes.join(' '));
// remove ng related attributes
if(attr.name.indexOf('ng-') === 0 || attr.name.indexOf('data-ng-') === 0) {
node.removeAttribute(attr.name);
}
})
});
alert(targetEl.outerHTML)
}
Here is a solution: http://jsfiddle.net/ga25hep2/2/

How to change the state of a sibling component in ReactJS for a Master/Slave checkbox system?

I'm curious how to implement a Master/Slave checkbox system. The approach I am currently taking is to have an owner/ownee (parent/child) relationship between the Master/Slave checkboxes. However, I'm curious if there's a way to accomplish this in React if the checkboxes were siblings instead. I see here in the docs that it says to use your own global event system. Can someone please explain/show me an example of what they mean by this? Thanks!
In my Backbone+React application I use backbone events when I do not have any other options to communicate states between the components. I am pretty sure you can find other minimal event libraries or build your own to communicate events if you need to.
Below is an example code from the jsFiddle http://jsfiddle.net/kb3gN/2239/ ,I have supplied for your scenario.
From the 'MasterCheckbox' component's onChange event I trigger a global application wide 'checkbox:clicked' event on the event object that's accessible to other components/views.
//Global event object
window.App = _.extend({}, Backbone.Events);
//MasterCheckbox component's change handler
handleChange: function () {
this.setState({checked: this.refs.master.getDOMNode().checked});
App.trigger('checkbox:clicked', this.refs.master.getDOMNode().checked);
}
Then on the 'SlaveCheckbox' component I subscribe to that event and change the state of the
'SlaveCheckbox' component
componentDidMount: function(){
App.on('checkbox:clicked', function(state){this.setState({checked: state}) }.bind(this));
}
You could wrap the two checkboxes into their own component to make a MasterSlaveComponent. Wasn't sure of the exact functionality you would want, but here is an example where turning on Master also turns on Slave.
var MasterSlaveCheckbox = React.createClass({
getInitialState: function(){
return {
master: false,
slave: false
}
},
handleMasterChange: function() {
var newMasterState = !this.state.master;
if(newMasterState) {
this.setState({master: true, slave: true});
} else {
this.setState({master: false});
}
},
handleSlaveChange: function() {
this.setState({slave: !this.state.slave});
},
render: function() {
return <div>
<div>
<input type="checkbox" checked={this.state.master} onChange={this.handleMasterChange} />
Master
</div>
<div>
<input type="checkbox" checked={this.state.slave} onChange={this.handleSlaveChange} />
Slave
</div>
</div>;
}
});
https://jsfiddle.net/crob611/z6ozz62a/1/

Can I use one ng-app inside another one in AngularJS

I have two ng-app
like ;
<div ng-app="app1" >
somexpression
<div ng-app="app2">
some more expression
</div>
</div>
is there any way to make it work?
when I make a nested ng-app it doesn't work
I know that I can use two different controller but I don't want to use two controllers
---- EDIT -----
The thing is;
angular.module('AppName', [
'angular-carousel'
])
SO I need somehow to change this ng-app to directive
From the AngularJS document, the answer is no
http://docs.angularjs.org/api/ng/directive/ngApp
AngularJS applications cannot be nested within each other.
And if not nested, then it's OK, someone already asked this question, refer here:AngularJS Multiple ng-app within a page
and the AnguarJS document
http://docs.angularjs.org/api/ng/directive/ngApp
Only one AngularJS application can be auto-bootstrapped per HTML document. The first ngApp found in the document will be used to define the root element to auto-bootstrap as an application. To run multiple applications in an HTML document you must manually bootstrap them using angular.bootstrap instead.
I found one tricky solution for this problem. The idea is that the "host" application have to somehow jump over the root element of nested application. I used directive for this:
angular.module("ng").directive("ngIsolateApp", function() {
return {
"scope" : {},
"restrict" : "AEC",
"compile" : function(element, attrs) {
// removing body
var html = element.html();
element.html('');
return function(scope, element) {
// destroy scope
scope.$destroy();
// async
setTimeout(function() {
// prepare root element for new app
var newRoot = document.createElement("div");
newRoot.innerHTML = html;
// bootstrap module
angular.bootstrap(newRoot, [attrs["ngIsolateApp"]]);
// add it to page
element.append(newRoot);
});
}
}
}
});
Example simple app:
// module definition
angular.module("testMod1",[])
.service("moduleService", function ModuleService() {
this.counter = 0;
this.getCounter = function() {
return this.counter;
};
this.incCounter = function() {
this.counter += 1;
}
})
.controller("ModuleCtrl", function(moduleService) {
this.getValue = function() {
return moduleService.getCounter();
};
this.incValue = function() {
moduleService.incCounter();
};
});
Now in the markup we can use the ng-isolate-app:
<!-- App instance 1 -->
<body ng-app="testMod1">
<div ng-controller="ModuleCtrl as ctrl">
{{ctrl.getValue()}}
<button ng-click="ctrl.incValue()">Click</button>
<!-- App instance 2 -->
<div ng-isolate-app="testMod1">
<div ng-controller="ModuleCtrl as ctrl">
{{ctrl.getValue()}}
<button ng-click="ctrl.incValue()">Click</button>
<!-- App instance 3 -->
<div ng-isolate-app="testMod1">
<div ng-controller="ModuleCtrl as ctrl">
{{ctrl.getValue()}}
<button ng-click="ctrl.incValue()">Click</button>
</div>
</div>
</div>
</div>
</div>
</body>
Working example on plnkr
This works in simple cases, I do not know how this will work on complex applications.
You can't use one ng-app inside another one in angularjs.
because AngularJS applications cannot be nested within each other.
https://docs.angularjs.org/api/ng/directive/ngApp

Resources