I've set up a general dialog directive with a title and apply/cancel buttons.
The dialog has an isolated scope.
The content of the dialog directive is transcluded and therefor it's scope is a sibling of the dialog scope:
From the Angular js docs:
However isolated scope creates a new problem: if a transcluded DOM is a child of the widget isolated scope then it will not be able to bind to anything. For this reason the transcluded scope is a child of the original scope, before the widget created an isolated scope for its local variables. This makes the transcluded and widget isolated scope siblings.
This presents a new problem for me though. The transcluded DOM should be able to respond the dialog when it is applied. Therefor I'd like to setup an 'applied' property on the dialog and let the transcluded DOM watch it. This is not possible though, because they are siblings!
Where am I going wrong?
I've run into something similar and there are 2 ways (that I know of) to access the transcluded scope directly.
The first is to create the scope yourself inside a compile function and then pass it to the transclude linking function along with a clone linking function:
function compileFn(tElement, tAttrs, transclude) {
return linkFn;
function linkFn(scope, element, attrs) {
scope = scope.$new();
scope.name = attrs.works1;
transclude(scope, function(clone) {
element.find('div').append(clone);
});
};
}
The second is to create a controller and inject the $transclude service which is pre-bound to a new scope. Your clone linking function will receive the new scope as its 2nd parameter:
function Controller($element, $attrs, $transclude) {
$transclude(function(clone, scope) {
scope.name = $attrs.works2;
$element.find('div').append(clone);
});
}
In both cases you'll have to provide a clone linking function to do the transclusion yourself instead of using ngTransclude.
See http://jsfiddle.net/dbinit/wQC7G/6/ for examples of both.
Oke, I think I've found a solution.
I've wrapped the actual dialog in a directive that defines the scope over the dialog.
The content of the dialog is still transcluded in the dialog, but since it will take it's parent scope from the parent of the dialog (!!) and not the dialog itself (transclusion works this way), this will work quite nicely.
Further, I can have the sg-import directive respond when the dialog is applied by using a &property on the dialog. When the dialog is applied, I have it evaluate the sg-apply function in context of the parent scope (the scoping is done automatically, I just have to call the method from the controller's apply() function).
<div sg-import>
<div
sg-dialog title="Import Photographs"
visible="show_import_dialog"
sg-apply="upload()"
>
<div class="drop-zone">
<div sg-photo title="{{ file.name }}">
</div>
<input type="file" multiple />
</div>
</div>
</div>
If you're willing to create a model in the common ancestor to act as a switchboard with $watch targets, you can use pre-existing facilities to having each directive mutate and/or watch that switchboard model. The component's mode of access and the content content's controller have two fery idfferent calls signatures for each scope, and there is a slight "gotcha" for the transcluded case.
Isolated Scope with Bi-Directional Binding
When registering the directive's isolate scope, =attrName" will cause examination of the domainName property named "attrName". Angular will set up bi-directional binding such that a change to the value in either scope's model affect the model in the sibling scope as well.
Example
In controller-parent.js:
module.controller( 'ParentController', function() {
$scope.switchboard = { };
}
In directive-sg-dialogue.js
return {
scope: {
isolatedPeer: "=dialogModel"
};
... in the directive metadata ...
<div ng-controller="ParentController">
<sg-dialog dialog-model="switchboard">
<div ng-controller="ChildController"></div>
</sg-dialog>
</div>
... in some applicaton view template, and ...
$scope.switchboard = { isApplied: false }
... in controller(s) bound to the application view template...
Then you are all set to ...
$scope.$watch( 'switchboard.isApplied', function(newValue, oldValue) { })
... in the common ancestor, and get a callback after ...
isolatedPeer.isApplied = true;
... in the isolated scope.
Prototypical Inheritance
As long as you don't explicitly set $scope.swtichboard in the transcluded child, you can access "switchboard.isApplied" from angular expressions in the transcluded child and have the interpolation engine "find" the value thaat was allocate dand stored by the parent controller in its own scope.
For example, the following callback will get invoked wheneve rthe pair dialogue box is closed:
$scope.$watch( 'switchboard.isApplied', function(newValue, oldValue) { } );
This works because the transcluded child is always given an basic scope, not an isolated scope.
Hope this is helpful!
Related
I have a simple component handled by <script type="text/x-template"..., and it has its own data object that has only one property to control whether or not to show a div.
I am getting the following warning in Console:
[Vue warn]: Property or method "showDiv" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option. (found in root instance)
Live demo: https://jsbin.com/zucaqog/edit?html,js,output
Js:
Vue.component('my-comp', {
template: '#my-comp',
data: function() {
return { showDiv: false };
}
});
new Vue({
el: '#app',
data: {}
});
If I add showDiv: false to the parent's data object, I no longer receive the warning. But, I'd like to avoid doing that since this data property is relevant only to the local scope of the component. I have a feeling if I put the html of the component as a string in the template: ... then it might work, but, I'd rather have it inside of a script.
It's because you have your child component's template definition as a child of the #app div. The parent component is seeing that there is a reference to the showDiv variable inside of its template, so it's throwing the error.
Pull that x-template outside of the #app div:
<div id="app">
<my-comp></my-comp>
</div>
<script type="text/x-template" id="my-comp">
<div>
Value of showDiv is {{showDiv}}.
<button #click="showDiv=!showDiv">Toggle showDiv</button>
</div>
</script>
I have a React component that is passing an object to a child element containing an input field to be modified via a passed function. The input field is changing the state of the child element but somehow, without invoking the passed function, the props object is being modified.
this is the render function of the parent class:
render: function() {
const newTriggerClass = this.state.item.triggerID === ""
? " new-trigger"
: "";
return (
<div className={newTriggerClass}>
<div className="triggers-detail">
<section className="trigger-info group">
<TriggerInfoEntry
item={this.state.item}
triggerState={this.state.editTriggerState}
columnHeaders={this.props.columnHeaders}
hideDetail={this.hideDetail}
editAction={this.props.editAction}
editToggle={this.editToggle}
_onChange={this._onInfoChange}
createAction={this.props.createAction}
deleteAction={this.props.deleteAction}/>
</section>
<section className="trigger-actions group">
<TriggerActionEntry
item={this.state.item}
actionState={this.state.editActionState}
editToggle={this.editToggle}
editAction={this.editTriggerAction}
actionIndex={this.state.selectedActionIndex}/>
</section>
</div>
<div className={"modal-overlay" + newTriggerClass}>
</div>
</div>
);
},
and the child TriggerActionEntry is setting it's own state.item to a clone of props.item.
propTypes: {
item: React.PropTypes.object.isRequired,
editAction: React.PropTypes.func.isRequired,
editToggle: React.PropTypes.func.isRequired,
actionState: React.PropTypes.bool
},
getInitialState: function() {
return {
item: assign({testy: true}, this.props.item),
actionIndex: 0,
deleteState: false,
editState: false
};
},
when I change the state of the child component (state.item) through an input, the parent state gets changed! I am not sure of the cause. Nothing that I have read online alludes to this behavior. Any help is appreciated.
UPDATE:
#garrettmaring The code I use to change state is only designed to work on the child state which is set in the getInitialState as a clone of this.props.item through assign which is my alias for object-assign which is an Object.assign ponyfill. The form to change item is actually one more child down. Here is the render of TriggerActionEntry
<div>
<TriggerActionForm
action={this.state.item.actions[this.state.actionIndex]}
index={this.state.actionIndex}
addNewAction={this.addNewAction}
editTriggerAction={this.editTriggerAction}
deleteTriggerAction={this.deleteTriggerAction}
editState={this.state.editState}
deleteState={this.props.actionState}
toggleEdit={this.toggleEdit}
activateEdit={this.activateEdit}/>
</div>
and here is the _onChange function of the grandchild TriggerActionForm
_onChange: function (e) {
e.preventDefault();
let newAction = assign({}, this.state.action);
const keyname = e.currentTarget.dataset.keyname;
if (keyname === "minDelay") {
newAction[keyname] = e.currentTarget.value;
} else {
newAction.args[keyname] = e.currentTarget.value;
}
this.setState({action: newAction});
if (!this.props.editState) {
this.props.activateEdit();
}
},
even when I unmount that component, the TriggerActionEntry state.item is modified, and shows up in the modified state when the component is mounted again...
UPDATE 2: OK, the problem is that neither Object.assign nor object-assign deeply clones objects. The classic! The actions were objects that were nested within item and rather than being cloned, another reference to the nested object was being created. Thanks for the help folks.
Im currently working on the same thing . The parent pass the data as a props to the children function. (im using hook instead of class). The purpose of the child component to process the data given from the parent and export as pdf or excel. Imagine the data pass in as an object that consists multiple array of objects . Each child object have an element of proj id which i would like to remove. The moment the data pass in , i have this
enter image description here
i set state with the data pass in from parent. When i gone throught some process and remove the the projid. When i try to access with any elements in the parent function. the whole thing gone broken.
In a previous post I asked how to add a bootstrap class to a Data View. The answer was to add the class to the "table.dataview" in a script block. After the table is created the class is applied and all is well.
But when I use a pager the formatting disappears. I am using a partial refresh on the pager to only refresh the data table but doing so means that the bootstrap class does not get applied to the table.
I believe I need to add an event handler that will attach to the refresh action of the dataView to add the class. However I cannot get the event handler to work.
My code for the event handler is below.
<xp:eventHandler refreshMode="partial" submit="true"
id="applyCSS" refreshId="dataView1" event="parialRefresh"
value="what" loaded="false">
<xp:this.binding><![CDATA[#{javascript:"pager1"}]]></xp:this.binding>
<xp:this.action><![CDATA[#{javascript:("table.dataview").addClass("table-striped table-hover table-bordered table-condensed")}]]></xp:this.action>
</xp:eventHandler>
Oliver, the rendered=false was simply a typo - I was testing something and needed to temporarily suppress that.
Oliver and Paul,
Last night I was able to get the partial refresh to work.
I ran across this post by Mark Roden which explained how to do it. There were two different ways to accomplish this, one less and one more efficient. The code I used is below.
<xp:scriptBlock id="scriptBlock3">
<xp:this.value><![CDATA[$('.dataView1PanelWrapper').on("DOMNodeInserted", function(){
$("table.dataview").addClass("table-striped table-hover table-bordered table-condensed")
})]]></xp:this.value>
</xp:scriptBlock>
However, and isn't there almost always a however in Xpages, I have some sortable columns in the view and clicking on the sort brings up the same problem! I lose the class assignment!
So now I would have to intercept that event too, right?
Concerned where this will end. Don't like the idea of DOM Manipulation, and only want to do it if I have too.
I started by using a simple view. It worked great, but for some reason the spacing was messed up in the pagers. I found that by moving the pagers out of the view itself, I was able to get the alignment issue fixed. I think it would be better just to use a view, as I can assign the class directly and won't have to do all this manipulation. It is however very good to know how to do this for the future.
Is that what you would suggest?
==================================================
I have tried Paul Withers suggestion using an output script. This works on the initial page load, but not on any other changes to the data view - when the pager fires, or sorting or any of that. I am close, but no cigar yet. Any suggestions?
<xp:scriptBlock id="scriptBlock5" loaded="false">
<xp:this.value><![CDATA[dojo.require("dojo.behavior");
Behavior = {
".dataview": {
found: function(node) {
dojo.addClass(node,".table-striped table-hover table-bordered table-condensed");
//node.addClass("table-striped table-hover table-bordered table-condensed");
}
}
}
dojo.ready(function() {
dojo.behavior.add(Behavior);
dojo.behavior.apply();
});
//Make sure that future pagers are also straightened out
dojo.subscribe("partialrefresh-complete", null, function(method, form, refreshId) {
dojo.behavior.apply();
});]]></xp:this.value>
</xp:scriptBlock>
Move your existing xp:scriptBlock with the working code inside a facet of the xe:dataView. Then the styling will get applied on initial load and on all partial refreshes.
You should call your CSJS stuff to add the class in the onComplete property of the event handler - hard to find, just highlight the event handler object in source code or outline and then open "All properties" to find the "onComplete" property. This event allows CSJS to be called.
BTW: why is the loaded property = false? The event will never we rendered.
dojo.behavior, dojo.ready and dojo.subscribe should allow you to manage this. dojo.behavior allows you to define a particular behaviour for a particular collection of elements which will be retrieved via a Dojo query. dojo.ready will (I believe) run the code when the page initially loads and dojo.subscribe("partialrefresh-complete", null, function(method, form, refreshId) {...} will run the code aftedr a partial refresh.
Here's sample code I used for converting a DataView's category column images to Font Awesome icons, so the Behavior = {...} bit will need amending.
Behavior = {
// Convert xp:pagers to Bootstrap
".catColumn a img": {
found: function(img_node) {
var imgSrc = dojo.getNodeProp(img_node, "alt").toLowerCase();
if (imgSrc.indexOf("collapse") >= 0) {
dojo.place('<i class="fa fa-minus-square"></i> ', img_node, 'replace');
} else {
dojo.place('<i class="fa fa-plus-square"></i> ', img_node, 'replace');
}
}
}
}
dojo.ready(function() {
dojo.behavior.add(Behavior);
dojo.behavior.apply();
});
//Make sure that future pagers are also straightened out
dojo.subscribe("partialrefresh-complete", null, function(method, form, refreshId) {
dojo.behavior.apply();
});
How to limit component events to it's parent/child components and prevent bubbling up.
I have the following two components:
ParentComponent.dart
#Component(
selector: "parent-component",
template: '<div>parent value: {{value}}<content></content></div>',
useShadowDom: false
)
class ParentComponent implements ScopeAware{
Scope _scope;
#NgAttr('value')
String value;
void set scope(Scope scope){
_scope = scope;
_scope.on('select-child').listen((ScopeEvent event){
value = event.data['value'];
});
}
}
ChildComponent.dart
#Component(
selector: "child-component",
template: '<div ng-click="select()">child: {{value}}</div>',
useShadowDom: false
)
class ChildComponent implements ScopeAware{
Scope _scope;
#NgAttr('value')
String value;
void select(){
_scope.parentScope.broadcast('select-child', {
'value': value
});
}
void set scope(Scope scope){
_scope = scope;
}
}
When one clicks on the child component, the parent updates its value.
But when i have more parent components they listen all to the same childs:
<!-- Parent 1 -->
<parent-component>
<child-component value="foo"></child-component>
<child-component value="bar"></child-component>
</parent-component>
<!-- Parent 2 -->
<parent-component>
<child-component value="herp"></child-component>
<child-component value="derp"></child-component>
</parent-component>
When i click the foo child-component of parent1 both parent components change their value to 'foo'.
I already tried playing with emit, broadcast. I know broadcast bubbles downwards to the leaf nodes and emit bubbles up. I also tried usind "scope.createChild()" but i think i miss something.
How can i create a scope in parent which is only visible to the child and vice versa?
Or how to use broadcast emit correctly?
When i understand the docs right i have to use emit() in child-component and not parentNode.broadcast() but i can't get it to work
Good question. Your confusion is coming from the fact that while components do create new scopes, those scopes are only available to the shadowDom (the html in the template: annotation).
In your example all 6 components - 2 parents and 4 children, create scopes that are child scopes of root scope. So when you are calling parentScope.broadcast you are firing event from rootScope down to all 6 scopes.
The easiest way to achieve the behavior you want is to directly inject ParentComponent into ChildComponent. Parent components are injectable to components in both their light and shadow DOMs.
class ChildComponent implements ScopeAware {
ParentComponent _p;
ChildComponent(this._p);
void select(){
_p.setValue(value);
}
...
}
Only downside is that makes the two components more tightly coupled to each other, which is ok if you are authoring both for your own application.
I'm doing a single page webApp with Marionette and RequireJs, basically it is a Layout view nested with a lots of ItemView and compositeView. I want the page change to a new design by one click, so I want to ask a best practice about changing the view's template.
say, I got my view like this, and its templates are included by requirejs's text plugin:
define([
'text!templates/designNo1/template.html'
], function(template) {
var MyView = Backbone.Marionette.ItemView.extend({
/*Template*/
template: template,
initialize: function() {
......
},
onRender: function() {
......
}
});
return SkillView;
});
every view and theirs subviews are defined like this. and their templates located in "template/designNo1" folder. now I got a set of new design located in "template/designNo2", and I want apply it to the page, but I found there is no chance to do this, because the views are already loaded by RequireJs and the template's path are hard-coded. of course, I can override the view's template when create the view's instance, but if I do so, I must load all the new design in the upper-module which create the instance, that looks bad, and the new design are keep coming, it gonna be a mess soon.
so, any advice?
From what I can tell, it sounds like you are wanting to change the template on a view depending on different circumstances. Marionette's ItemView has a getTemplate() function that is called when rendering it. You can specify a function that determines which template to render from there.
Another approach might be to simply change the template of the view and re-render it. You could do that on a click event easily:
HTML
<div class="content"></div>
<button class="btn">Change Template</button>
Javascript
var template1 = '<div>Template 1</div>';
var template2 = '<div>Template 2</div>';
var ItemView = Backbone.Marionette.ItemView.extend({
template: template1
});
var itemView = new ItemView({ el: '.content' });
itemView.render();
$('.btn').click(function(e) {
e.preventDefault();
itemView.template = template2;
itemView.render();
});