In Angular JS, is there a way to watch for changes to the DOM without using scope.watch? - angularjs

I am attempting to create an angularjs bootstrap accordion that scrolls the accordion to the top when opened.
These solutions are close to what I would like to do:
AngularJS / ui-bootstrap accordion - scroll to top of active (open) accordion on click
https://www.peterbouda.eu/making-an-angular-ui-bootstrap-accordion-scrolling-to-top-when-opening.html
However, they use a timeout or scope watches. I would like to avoid using these unless absolutely necessary.
Is there a way to accomplish this without using $watch or setTimeout?
Here is a plunk of what i am trying to do, this is using the $watch: https://plnkr.co/edit/XQpUdrdjqaCGom4L9yIJ
app.directive( 'scrollTop', scrollTop );
function scrollTop() {
return {
restrict: 'A',
link: link
};
}
function link( scope, element ) {
scope.collapsing = false;
var jqElement = $( element) ;
scope.$watch( function() {
return jqElement.find( '.panel-collapse' ).hasClass( 'collapsing' );
}, function( status ) {
if ( scope.collapsing && !status ) {
if ( jqElement.hasClass( 'panel-open' ) ) {
$( 'html,body' ).animate({
scrollTop: jqElement.offset().top - 30
}, 500 );
}
}
scope.collapsing = status;
} );
}

The directive can be simplified to:1
app.directive( 'scrollTop', function scrollTop($timeout) {
return {
restrict: 'A',
link: postLink
};
function postLink(scope, elem, attrs) {
elem.on("click", function(e) {
if (scope.status.isOpen) {
$timeout(function() {
$( 'html,body' ).animate({
scrollTop: elem.offset().top - 30
}, 500 );
});
}
});
}
})
<uib-accordion>
<div heading="Section Title" is-open="status.isOpen"
ng-repeat="section in vm.sections"
scroll-top
uib-accordion-group>
<uib-accordion-heading>
<div ng-class="{isOpen: vm.isOpen}">
<h3>{{section.sectionTitle}}</h3>
<p>{{section.sectionSubHeader}}</p>
</div>
</uib-accordion-heading>
<div class="clearfix">
<b>Index+1={{$index+1}}</b>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor
in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
</div>
</div>
</uib-accordion>
The $timeout is necessary because the browser needs to render the DOM with the newly opened and closed elements before it can compute the proper scroll offset.
The DEMO on PLNKR

I have found a way to do this from the controller.
I added a function that is triggered on ng-click to report the is-open status of the accordion.
Using the component lifecycle hook $doCheck I was able to watch for changes to the state of vm.isOpen. $doCheck runs on the end of every digest cycle, so I did not need to set a $scope.watch or $timeOut
The $doCheck runs essentially the same code as the directive in the question
app.controller('homeController', function($state, $element, sections, $transitions) {
var vm = this;
vm.$onInit = function() {
vm.sections = sections.getSections();
};
function updateOpenStatus() {
vm.collapsing = false;
vm.isOpen = vm.sections.some(function(item) {
return item.isOpen;
});
}
vm.$doCheck = function() {
if (vm.isOpen) {
var elem = $element.find('.panel-collapse');
var status = elem.hasClass('collapsing');
if (vm.collapsing && !status) {
var parentElem = elem.closest('.panel-open');
if (elem.parent().hasClass('panel-open')) {
$('html,body')
.stop()
.animate({
scrollTop: parentElem.offset().top - 52
}, 'fast');
}
}
vm.collapsing = status;
}
};
});
I updated the uib-accordion to call the function in the controller
<uib-accordion>
<div heading="Section Title" is-open="section.isOpen" ng-repeat="section in vm.sections" scroll-top uib-accordion-group>
<uib-accordion-heading>
<div ng-class="{isOpen: section.isOpen}" ng-click="vm.toggleOpen()">
<h3>{{section.sectionTitle}}</h3>
</div>
</uib-accordion-heading>
<div class="clearfix">
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor
in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
</div>
</div>
</uib-accordion>
Updated Plnkr: https://plnkr.co/edit/5EqDfmVOa0hzFfaQqdI0?p=preview

Related

framer-motion: element stops animating when changing state the 3rd time

Whenever addStory() is called, it displays and animates text, but on the 3rd time it only displays the text and not animate it. I tried changing the text and when I hit Ctrl S it animated it. How do I keep animating them whenever they are shown? I've been trying to solve this problem since yesterday but could not find a solution.
sandbox https://codesandbox.io/s/frosty-smoke-nt0we?file=/src/components/Game.tsx
ps: addStory() displays different text whenever it is called, so it should re-render and animate.
const addStory = (newStory: string) => {
setStory(
<>
{newStory === undefined ? (
<span>{story}</span>
) : (
<>
{newStory.split('\n').map((str, index) => (
<motion.p
key={index}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 1 }}
>
{str.length > 1 && '=> '}
{str}
</motion.p>
))}
</>
)}
</>
);
};
the addStory() function is called like this
const whichChoice = () => {
if (choice === 'a1') {
return mainStory.a1;
} else if (choice === 'a2') {
return mainStory.a2;
} else if (choice === 'b1') {
return mainStory.b1;
} else if (choice === 'b2') {
return mainStory.b2;
} else if (choice === 'c1') {
return mainStory.c1;
} etc......
else return;
};
useEffect(() => {
const narrate = whichChoice();
addStory(narrate);
}, [choice]);
the choice is changed using onClick via useState hook
the mainStory.a1 etc, returns a string from an object. It looks like this:
export const mainStory: any = {
a: '=> Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor',
a1: '\n Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.',
a2: '\n nisi ut aliquip ex ea commodo consequat',
b1: '\n Duis aute irure dolor in reprehenderit \n in voluptate velit esse cillum dolore eu fugiat nulla pariatur \n Excepteur sint occaecat cupidatat non proident',
b2: '\n sunt in culpa qui officia \n deserunt mollit anim id est laborum. \n Sed ut perspiciatis unde omnis iste natus \n voluptatem accusantium doloremque laudantium'
etc...
}
I think the duration property in motion.p indicates that the animation runs from 0secs to 1secs. You might be looking for transition={{yoyo: 10}}
EDIT
Just understood, that its not triggering on consecutive renders, it seems the react DOM tree gets updated with each state change, for this, Framer needs an AnimatePresence to inform framer that the tree has changed.
Reference : https://www.framer.com/docs/animate-presence/

Api-platform accessing embedded relations in hydra:member

I'm using Api-Platform and React as client.
I have a Forum entity which contains other objects : an author (User entity) and a category (Category entity).
I embedded these entities with #Groups annotations to retrieve them more easily.
For instance, when I fetch /api/forums/ on Postman I get this result :
{
"#context": "/api/contexts/Forum",
"#id": "/api/forums",
"#type": "hydra:Collection",
"hydra:member": [
{
"#id": "/api/forums/79",
"#type": "Forum",
"id": 79,
"subject": "Sujet numéro 0",
"message": "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
"category": {
"#id": "/api/categories/183",
"#type": "Category",
"title": "Category 2"
},
It seems good : we can see my category object is linked to Forum in hydra:member array, and a "title" field.
But I noticed that the result I really get is different in my react app for all my nested objects.
I only get the IRIs..
For instance with the same request :
...
fetch('forums')
.then(response =>
response
.json()
.then(retrieved => ({ retrieved, hubURL: extractHubURL(response) }))
)
.then(({ retrieved, hubURL }) => {
retrieved = normalize(retrieved);
...
I get this result :
{
"#context": "/api/contexts/Forum",
"#id": "/api/forums",
"#type": "hydra:Collection",
"hydra:member": [
{
"#id": "/api/forums/79",
"#type": "Forum",
"id": 79,
"subject": "Sujet numéro 0",
"message": "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
"category": "/api/categories/183",
Every other fields have disapeared on "category", and I don't get why.
Could anyone help me with that ?
Thanks in advance
I found the solution (how silly of me), so I share you the tip :
In src/utils/dataAccess file, there is a normalize function :
export function normalize(data) {
if (has(data, 'hydra:member')) {
// Normalize items in collections
data['hydra:member'] = data['hydra:member'].map(item => normalize(item));
return data;
}
// Flatten nested documents
return mapValues(data, value =>
Array.isArray(value)
? value.map(v => get(v, '#id', v))
: get(value, '#id', value)
);
}
It appears that this function is called after each fetch :
return fetch(id)
.then(response =>
response
.json()
.then(retrieved => ({ retrieved, hubURL: extractHubURL(response) }))
)
.then(({ retrieved, hubURL }) => {
//retrieved = normalize(retrieved);
dispatch(loading(false));
dispatch(success(retrieved));
....
As you can see, I just commented the normalize line (useless in my case). Do that wherever it is needed, and everything should work again !

How to fetch single data in Angularjs with Laravel5

I face an issue while retrieving single row/data from angular to html. I need to print the title. Below is the code of angular controller (partial):
vm.showJoke = function(joke){
console.log(joke);
$http.get('http://localhost:8000/api/v1/jokes/' + joke, {
//body: joke.joke,
//user_id: $rootScope.currentUser.id
//user_id: localstorageUser.role_id
}).success(function(response) {
//console.log(response.data.joke);
vm.jokes_show = response.data.joke;
console.log(vm.jokes_show);
$scope.MessageAll = 'Joke shows! ';
}).error(function(){
console.log("error");
$scope.MessageAll = 'Please try again';
});
}
Below is my HTML codes:
<span ng-hide="showEnabled">
<span class="glyphicon glyphicon-th-large" data-ng-click="jokes.showJoke(joke.joke_id)"></span>
</span>
<span ng-show="showEnabled" style="background-color:powderblue;">
<p style="background-color:powderblue;" ng-model="jokes_show"></p>
</span>
Here is the response I found below:
{joke_id: 3, joke: "Quia in enim voluptates neque. Ullam eligendi ipsu…iente qui perferendis libero. Ut aut qui sint et.", submitted_by: "admin"}
joke
:
"Quia in enim voluptates neque. Ullam eligendi ipsum eaque magni sit fugit in. Voluptate sapiente qui perferendis libero. Ut aut qui sint et."
joke_id
:
3
submitted_by
:
"admin"
See the image for the same too.
Your help most welcome.

How to pass an Argument to Method in Perl

I'm trying to use this Perl Method: HTML::Highlight - A module to highlight words or patterns in HTML documents.
The methode itself isn't acctualy the problem but the way how I can pass a Attribute.
Example which works:
use HTML::Highlight;
$text = 'Lorem ipsum Velit ullamco ex anim quis Duis laboris ut proident velit eu dolor Ut amet proident aliqua minim officia sunt commodo veniam dolor id reprehenderit reprehenderit non nulla incididunt mollit exercitation minim commodo ut quis laboris ex proident.';
# create the highlighter object
my $hl = new HTML::Highlight (
words => [
'ex',
'ul',
],
wildcards => [
undef,
],
colors => [
'red; font: bold',
],
debug => 0
);
my $hl_document = $hl->highlight($text);
print $hl_document;
What I'd like to do is something like this:
use HTML::Highlight;
$text = 'Lorem ipsum Velit ullamco ex anim quis Duis laboris ut proident velit eu dolor Ut amet proident aliqua minim officia sunt commodo veniam dolor id reprehenderit reprehenderit non nulla incididunt mollit exercitation minim commodo ut quis laboris ex proident.';
# create the highlighter object
#keywords = "ex", "ul";
my $hl = new HTML::Highlight (
words => #keywords,
wildcards => [
undef,
],
colors => [
'red; font: bold',
],
debug => 0
);
my $hl_document = $hl->highlight($text);
print $hl_document;
As you can see in the code snippet above, I'd like to pass an existing array to the object.
How can I do that correctly?
Currently I get an exeption like that:
HTML::Highlight - "words" and "wildcards" parameters must be references to arrays at C:\Skripts\Perl\syntax_highlight.pl line 8.
As said in error message, pass a reference:
my $hl = HTML::Highlight->new(
words => \#keywords,
# here __^
wildcards => [
undef,
],
colors => [
'red; font: bold',
],
debug => 0
);

Backbone: Can't access 'this.model.*' in view

I'm new to Backbone and struggling with what appears to be a basic problem. I have a route set up to take in a parameter, use it to locate a model (hard-coded at this point), and render the model data in a view.
Working with a parameter of "project1", in the render function of my view I can access this.model and project1.attributes and project1.get('name') but I cannot access this.model.attributes or this.model.get('name') or use this.model.toJSON(). (Note: trying to access any of those latter ones causes the app to bomb.)
Is this a "this" bind problem? Any help is appreciated.
var Router = Backbone.Router.extend({
routes: {
"work/:project" : "project"
},
project: function (project) {
$('#content').html(new ProjectView({model:project}).render().el);
}
});
var ProjectView = Backbone.View.extend({
id: "project",
render: function () {
$(this.el).html(this.template(this.model.toJSON()));
return this;
}
});
var Project = Backbone.Model.extend({});
var project1 = new Project({
name: "Project1",
description : "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."
});
As the comments have already mentioned, your basic problem is that you're passing a string to a View, but that View is expecting a Model. This line:
$('#content').html(new ProjectView({model:project}).render().el);
Is the problem: project needs to be an instance of Backbone.Model, but it's not. That line should instead look something like this:
var project1 = new Project({
name: "Project1",
description : "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."
});
$('#content').html(new ProjectView({model: project1}).render().el);

Resources