AngularJS Directive - re-run link function on scope parameter change - javascript

I have a directive that builds a set of nested <ul> elements representing a folder structure. I used the link function to create the new DOM elements and append them to the directive instance element:
function link(scope, iElement, iAttr) {
var rootElement = buildChildElement(scope.tree);
iElement.append(rootElement);
}
Elements within the <ul> tree are wired with jQueryUI's drag/drop interactions that call a function on the Controller housing the directive to update the scope parameter based on the drag & drop events.
I would like the <ul> tree to automatically update when there is a change to the scope parameter. I have tried a watch function within my link function:
scope.$watch('tree', function(newTree, oldTree) {
var newRoot = buildChildElement(newTree);
iElement.contents().remove();
iElement.append(newRoot);
}
This works to a certain extent, but the call to remove() fires off the $watch() method a second time which ends up reverting my Controller changes. If I comment out the remove(), I can see that a new <ul> tree is written that properly reflects the changes to the parameter made in the Controller.
The double firing $watch() makes me think I'm going about this wrong. Without it, my parameter is properly updating but my <ul> doesn't update (the dropped element stays where it was dropped).
What's the correct way to make sure your directive is refreshed on a change in one of the scope parameters?
Should I be using the compile function and building the <ul> tree based on the attributes array instead of using the link function?

Your approach is very jQuery-style. I think you'll find that you're working against Angular in this case. sh0ber is right with his/her question; you should post a demo or something, or at least some sample code so you can have an effective answer.
I think you want to make a recursive tree directive. Check out this SO answer for some interesting approaches to this. The main idea is that watch is unnecessary. Simply change the object and Angular will take care of the rest. The most efficient thing is to change the specific node objects directly rather than replacing the whole object, but that will work too.

scope.$watch('tree', function(newTree, oldTree) {
var newRoot = buildChildElement(newTree);
iElement.contents().remove();
iElement.append(newRoot);
},**true**)
I think you can have a try and reference the watch API for more information
Here is another artical
http://www.bennadel.com/blog/2566-scope-watch-vs-watchcollection-in-angularjs.htm

Related

How to get the component that rendered a dom element with Vue.js

How to get the component that rendered a dom element with Vue.js ?
For example, suppose you want to retrieve which component "owns" a dom element that the user has selected, how would you do ? (it seems to be implemented in the dev tools, but I can't find a way neither in the documentation, neither on SO)
(I mean, given the DOM element, I know how to retrieve what element is selected)
DISCLAIMER : This may not be the right solution for common use cases. Always prefer handling event & co. in the root component using direct sub-component reference if you can
I do not know if this is safe or officially supported, but assuming you're trying to access the component from some external-to-Vue code, this will return the VueComponent object for a given DOM element (substitute your own DOM selector as needed):
document.getElementById('foo').__vue__
If used on the app's root element, it will instead return the Vue constructor object.
(This appears to work in both Vue 1.x and 2.x.)
This is possibly not the most elegant solution, but you can use mixins to achieve this:
var elemOwner = {
mounted: function() {
this.$el.setAttribute("isVueComponent", this.$options.name);
}
};
As long as you set the mixin to the components you need it in, when you click an element you can test the attributes to see if there's a component name in there.
See this codepen for a fuller example: https://codepen.io/huntleth/pen/EpEWjJ
Clicking the smaller blue square will return the component name of the component that rendered it.
EDIT - It should be noted though that this obviously would only work if the element is actually inside that components root element. I think that would be the case for almost all uses.
Getting this.$parent refers to the parent of a component.

Explanation asked about a Javascript / Angular line

In a factory I construct a HTML page. This page can contain a form, so I want to get a handle on the FormController. After some Googling I've got everything working with this line of code (html is all the html in a string in a jquery selector):
html.find("input").eq(0).controller('form');
I understand that:
find(): it is going to find all the input elements;
eq(): I suppose this will select the first found item of the find list;
controller(): this part is unclear. I find it hard to find some documentation about this. What I do know is that you can pass ngModel or form. When passing ngModel you get the FormController of the specified control, thus not the whole form. And when specifying form you get a reference to the whole form.
So, I understand the most of, but I still don't get if controller() is an Angular function or Jquery function and how/when you can use this method.
There is no concept of "controller" in jQuery: controller() is obviously an Angular function. Here is the documentation:
controller(name) - retrieves the controller of the current element or its parent. By default retrieves controller associated with the ngController directive. If name is provided as camelCase directive name, then the controller for this directive will be retrieved (e.g. 'ngModel').
controller() is a method added by Angular to the jQuery object. It returns the Angular controller associated with the element. See the docs including other extra methods here...
http://docs.angularjs.org/api/ng/function/angular.element

Override Marionette.Region's getEl method in Backbone.Marionette

I've set up a project where I've extended a Backbone.Marionette.Layout that contains two different regions. This layout can be used as a component throughout the application. In particular, the regions are set up like the following.
regions : {
masterRegion : { selector: '[data-region=master]' },
slaveRegion: { selector: '[data-region=slave]' }
},
In particular, I'm using a data-region selector to inject the view I'm interested in.
When such a layout is used in a tree structure views are duplicated since getEl function adresses the wrong region to inject the view. Obviously it's my fault and within Marionette (v1.1.0) doc the following is written.
override the getEl function if we have a parentEl this must be
overridden to ensure the selector is found on the first use of the
region. if we try to assign the region's el to parentEl.find(selector)
in the object literal to build the region, the element will not be
guaranteed to be in the DOM already, and will cause problems
where getEl is defined as
getEl: function(selector){
return Marionette.$(selector);
}
So, my question is the following. What does this mean? How can I override this method? Where is the correct to perform such an override?
Hope it's clear.
Here's my understanding of this:
the points below apply to the case where the layout is contained within another element ("if we have a parentEl")
the first time you use a region, Marionette needs to select the proper DOM element to populate, according to the selector string ("ensure the selector is found on the first use of the region")
you can't simply look for the selector in the parentEl ("if we try to assign the region's el to parentEl.find(selector) in the object literal"), because the DOM element we want isn't necessarily in the DOM yet ("the element will not be guaranteed to be in the DOM already")
In other words, the first time you use a region (e.g. with a call to the show method), Marionette needs to build a region instance and associate it with the correct DOM element (specified by the selectorattribute).
However, before Marionette can look for the DOM element within the containing parent element, it must ensure that all required DOM elements (most importantly the one we're looking for) have loaded.
Does that make more sense to you?
Edit based on flexaddicted's comment.
Could you suggest me a the correct way to achieve this? Is there any
manner to override the method below?
I don't think you need to override this method. The comment indicates why the DOM element is fetched that way instead of by direct assignment when the region is built, but it should still work properly with a tree structure (since parents can still be determined properly).
I think the problem might be with your region selector: as it is "generic", it can potentially match multiple elements (as opposed to selecting with an id attribute that should match only 1 element), and could be matching a DOM element you're not expecting such as a child view. (This of course depends on when Marionette looks at the DOM to fetch the selector.)
Also, I'd consider using a composite view for your tree structure needs if possible. See http://davidsulc.com/blog/2013/02/03/tutorial-nested-views-using-backbone-marionettes-compositeview/ and http://lostechies.com/derickbailey/2012/04/05/composite-views-tree-structures-tables-and-more/

JavaScript to jQuery for KendoUI

I've been using KendoUI and have been using they're command functions. However to call JS I must call named jS functions. No huge deal. When I use the "This" key word it brings back the entire grid and I mus find a value of a child from a sibling of the same parent elements and i wound up doing this ugly thing. The question I have is how can I turn this "thing" into something jqueryable readable and comprehensible
function AddRole(e) {
var $ParentNode = e.target.parentNode.parentNode.children[1].children[0].getAttribute("value", 0);
}
Sorry, but you have other problems.
If you rely on such a structure e.target.parentNode.parentNode.children[1].children[0], your Markup and JS do not scale at all.
Use the oppurtunity to create scalable and consistent code. Or at least, set some id, class or html5 data attribute on the children[0] element in order to identify it properly.

EmberJS - Adding a binding after creation of object

I am trying to bind a property of an object to a property that's bound in an ArrayController. I want all of this to occur after the object has already been created and added to the ArrayController.
Here is a fiddle with a simplified example of what I'm trying to achieve.
I am wondering if I'm having problems with scope - I've already tried to bind to the global path (i.e. 'App.objectTwoController.objectOne.param3') to set the binding to. I've also tried to bind directly to the objectOneController (which is not what I want to do, but tried it just to see if it worked) and that still didn't work.
Any ideas on what I'm doing incorrectly? Thanks in advance for taking the time to look at this post.
So in the example below (I simplified it a little bit, but same principles apply)... The method below ends up looking for "objectOne" on "objectTwo" instead of on the "objectTwoController".
var objectTwoController: Em.Object.create({
objectOneBinding: 'App.objectOne',
objectTwoBinding: 'App.objectTwo',
_onSomething: function() {
var objectTwo = this.get('objectTwo');
objectTwo.bind('param2', Em.Binding.from('objectOne.param3'));
}.observes('something')
});
The problem is that you can't bind between two none relative objects. If you look in the "connect" method in ember you will see that it only takes one reference object (this) in which to observe both paths (this is true for 9.8.1 from your example and the ember-pre-1.0 release).
You have few options (that I can think of at least).
First: You can tell the objects about each other and in turn the relative paths will start working. This will actually give "objectTwo" an object to reference when binding paths.
....
objectTwo.set('objectOne', this.get('objectOne');
....
Second: You could add your own observer/computed property that will just keep the two in sync (but it is a little more verbose). You might be able to pull off something really slick but it maybe difficult. Even go so far as writing your own binding (like Transforms) to allow you to bind two non-related objects as long as you have paths to both.
_param3: function(){
this.setPath('objectTwo.param2', this.getPath('objectOne.param3');
}.observes('objectOne.param3')
You can make these dynamically and not need to pre-define them...
Third: Simply make them global paths; "App.objectOneController.content.param3" should work as your binding "_from" path (but not sure how much this helps you in your real application, because with larger applications I personally don't like everything global).
EDIT: When setting the full paths. Make sure you wait until end of the current cycle before fetching the value because bindings don't always update until everything is flushed. Meaning, your alert message needs to be wrapped in Ember.run.next or you will not see the change.

Categories

Resources