I have a component which is shown in a particular view.
I am now taking that component using <compose> to show it in a pop-up as well.
The component has several components within it.
example code would be like this:
<div if.bind="something">
<inner-component if.bind="something else"></inner-component>
</div>```
this is obviously not the actual code, I am just showing how it is set up. The reason being That when I added the outer if.bind The bindingContext supposed to be coming in through bind() in the inner component is now undefined BUT this is only in the original view of the component, NOT in the new pop-up view. Before I added the outer if it worked fine for the original view (the pop-up view didnt exist yet)
What am I missing?
Related
I have a bootstrap modal that will display info about a parent document. The modal then has next/prev buttons that can rotate around a list of parent documents and reactively change the info displayed on the modal. It does this by simply changing a ReactiveVar of the current parent's onClick of next/prev. This works like a charm.
My problem is that each parent shows some child data as well. The child data is an array of embedded documents. And each document's property has some HTML inputs. I'm trying to pre-populate the values of the html inputs. However, I'm finding this does not work. id and foo are properties on the child document(s).
A call to {{parent}} is a Helper method that returns the current parent from MiniMongo. This seems to work fine as the parent's metadata changes reactively.
{{#each parent.childrenArray}}
<input type="text" id="{{id}}" value="{{foo}}">
{{/each}}
So the issue is that the HTML value does not change. Clicking next will still show the old child's value. I understand there's no reactivity for embedded documents and I'm sure this is the problem. Strangely, upon inspecting, I am in fact seeing the input's id being changed though. Does anybody know the solution to this? Thanks!
This seems to happen when I try to save the children back to the DB. I.e. hitting next/prev should save whatever value users puts into the HTML input before moving on to the next Parent and pre-loading its children
If parent is a helper, then I don't think you can use the subscript notation to get its properties, at least not reactively. However you should be able to use this instead:
{{#with parent}}
{{#each childrenArray}}
<input {{attributes}}>
{{/each}}
{{/with}}
Update: The other thing that is probably required is to use an attribute helper. I've also updated the code above to use this.
Template.yourTemplateName.helpers({
attributes() {
return {
type: 'text',
id: this.id,
value: this.foo
};
}
});
Update 2:
Working "offline" in a git repo by the OP I realized that the modal body was better done as a separate template, such that the data context for that template would change reactively. The part of the code that was at odds is not represented in the question, so read this question and answer with a grain of salt.
Currently I'm learning VueJS and I'm working with http://vuematerial.io.
I have build an application with several pages - each of them contains a sidebar (the drawer component https://vuematerial.io/components/drawer).
Since I don't want to copy and paste the same drawer component code over and over again in each page, I just want to create one sidebar component, which I'll then import on each page.
So far, so good.
This is working fine.
But now - I want to be able to open and close the sidebar.
Just before, when the component was directly in the page, it was easy - just a variable assignment with a boolean value to whether show or hide the sidebar.
But now, it seems very hard for me, to synchronize the property over the components.
Let me show you the current new code to clarify what's the problem:
So, this is the page component:
<md-toolbar class="md-primary">
<md-button class="md-icon-button" #click="showSidebar=true">
<md-icon>menu</md-icon>
</md-button><span class="md-title">Dashboard</span>
</md-toolbar>
<Sidebar v-bind:showSidebar="showSidebar"></Sidebar>
So that's the Vue Structure - you can see - I want to bind the showSidebar property.
That's how I'm implementing it within the page
import Sidebar from './sidebar.vue';
export default {
data: function () {
return {
showSidebar: false
}
},
components: {
Sidebar: Sidebar
},
And now the Sidebar component itself:
<md-drawer v-bind:md-active.sync="showSidebar">
The sidebar component then fetches the value over a property like this:
export default {
name: 'sidebar',
props: ['showSidebar'],
And this seems to work!
I can click on the menu button on the page, set the property to true - and the sidebar shows! Great! But.. When I click outside of this sidebar, this warn message appears - and - furthermore - I can't reopen it again on a new click. It closes, but I can't open it again, until I completely reload the page.
How can I be able to solve that?
I have also thinked about using events, since the drawer component seems to listen on events, but I wasn't successful.
That's the current code from the drawer component: https://github.com/vuematerial/vue-material/blob/dev/src/components/MdDrawer/MdDrawer.vue
I hope that it was clear, what my problem is.
I hope, anyone can help me out.
This is my first question here - so please be nice :)
/EDIT:
Opps, here is the warn message:
[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "showSidebar"
I'm not a Vue pro - still learning it myself - but I think I can see what is going on here.
I think the warning in particular is because you have a prop AND a data property of the same name. Try removing the data setting. You can change the props setting to this:
props: {
showSidebar: {
type: Boolean,
default: false
}
}
See if that fixes it. Also, given how you seem to be using this, I'd suggest looking into using Vuex. The documentation is good and it could really help manage your app. Possibly even fix that issue with reopening the drawer.
Edit:
After reviewing this user's code more closely (connected with them on discord), I've determined the issue is that while the process of opening the sidebar was managed by a property on the parent component, the act of closing it was strictly occurring in the child. When you have data bound like that from parent to child, you need to be sure to update the source (the parent component) of the relevant changes. In Vue, those changes are only pushed one direction. To pass info up to the parent, you have to $.emit events.
My current recommendation is to add a custom event to the sidebar component tag on the parent component:
<Sidebar v-bind:showSidebar="showSidebar" v-on:hide-sidebar="showSidebar=false"></Sidebar>
And then change the close tag in the child component to this:
<span class="md-title" #click="$emit('hide-sidebar')">FleaMaster</span>
Hopefully this information helps someone else as well!
I have a SPA in Angular Material which is displaying an mdToolbar element with a hamburger menu + left sidenav. That menu at the moment resides in my index.html where I have also set-up a <ui-view> element to render the view.
Now I have a view with a mdList in it. When the user selects some items, I want a delete icon to appear in the toolbar. That delete icon should be linked to the delete action of my controller which is of course specific to the view loaded, not to index.html.
I want to know what a recommended pattern for this would be. I can think of some ways to do it, but those are ugly. I was thinking in the direction of being able to have some placeholder area in the toolbar which I can replace with contents from my view, where the element actions (ngClick) are linked to the actions of the view controller. Does anyone know a good tutorial or codepen-like example of how to do this?
Update
I've now got something implemented that I'm happy with, but it's not quite there yet. What I did is create a menuService which is injected in the controller behind my menu (it's not a separate view, could be but doesn't make a difference in this scenario). The menu controller binds to this service and other services can inject stuff in it. In my test scenario, I inject a string which is then displayed in the toolbar, all ok.
The only thing I now need to do is instead of a string, inject a button with an event handler which goes back to the controller of the view. I'm not quite certain how to do that yet.
Another update
So I created this class:
export class CommandButton {
svgSrc: string;
click: () => void;
}
which I can inject into my menuService and then binds to the menu controller. Works fine for the icon (the button appears) but as one might expect (I did), the click function doesn't work. I set this in the view controller as follows:
var deleteButton = new Services.CommandButton();
deleteButton.svgSrc = 'icons/ic_delete_24px.svg';
deleteButton.click = this.deleteLogs;
this.menuService.setButtons([deleteButton]);
And the code for this.deleteLogs is simply:
deleteLogs() {
console.log('deleting logs');
}
Code for the buttons:
<div ng-repeat="button in ctrl.buttons">
<md-button ng-click="button.click">
<md-icon md-svg-src="{{button.svgSrc}}" class="md-icon md-24"></md-icon>
</md-button>
</div>
What I hoped for is that this would trigger the deleteLogs method in the view controller, but that's not the case. I need data from the view as that is where the items are selected. As far as I can see there's just nothing happening so the binding fails somewhere. What would be a good way to make sure the click event makes to to the view controller function? I could do a $rootscope.$broadcast but that feels hacky.
Last update
Never mind, I found my own bug. The binding of the event was incorrect, should have been (note the parenthesis):
<md-button ng-click="button.click()">
In the meantime I've figured out a nice way to do this. The post itself now also contains the answer.
The code at this plnkr has a modal which pops up when a user clicks on a "Click to take quiz" button which calls a controller method that in turn calls a modal service. To get the plnkr to work, click anywhere in the code and press the space bar to add white space in a way that does not effect syntax. This will trigger plnkr to re-initialize the app and make the modal pop up after you click the button.
The problem is that the text printed in the modal does not update dynamically when timeLeft variable counts down. And also, the user's button click does not update the quizAnswer variable. In short, the modal is not able to talk interactively with the calling controller and view.
What specific changes need to be made to the plnkr to get the modal text to show the dynamic countdown, and to get the modal buttons to change the value of the $scope.quizAnswer variable?
Also, I have been carefully reading the documentation at this link. I think that the answer may be related to:
1.) $uibModal's options parameter passed in open(options) contains the parameter scope that defines the parent scope to be used for the modal's content, and also property bindToController which, when set to true, binds the scope property to a specific controller defined by controllerAs.
2.) The open(options) method returns a modal instance, which includes close(result) and dismiss(reason).
I suspect that the solution lies in these methods and parameters, but I am looking for good examples and would appreciate some experienced eyes looking at this problem.
NOTE: The solution to this came in the comments below the accepted answer, especially the link to another posting that contains 2 lines of code for emitting the modal button click's results back to the parent controller.
You have a number of issues.
First, takeQuiz at navigation.js - line 16, should be attached to $scope, not this, since this will mutate depending on context.
Second, $scope.$apply and $scope.$digst(); at navigation.js - lines 29/30 are unnecessary since you will already be in a digest cycle. They should be removed else they'll trigger an error.
Finally (and this is the meat of your issue), you are misunderstanding how modal options are bound across when creating a modal instance. It is NOT two-way binding; it is a single extends from one object to another. As a result, trying to bind to the options (or creating a concatenated string with the timeRemaining) will not update once it's bound across.
Instead, one possibility is to create an event handler inside of the modal and broadcast on each tick, updating the modal. In addition, if you pass the body text as prepend and append text, it is easier to insert your timestamp value:
You will need to inject (and broadcast from) $rootScope in your navigationController, since the modalService is registered somewhere very high in the scope chain.
On each tick, broadcast the time remaining navigation.js:
$rootScope.$broadcast('timeRemainingTick', $scope.timeRemaining);
In your modalService.js, register to receive the event inside of the controller assignment:
var timeRemainingUnbind = $scope.$on('timeRemainingTick', function(event, newTick) {
$scope.modalOptions.timeRemaining = newTick;
});
Finally, make sure that you unbind the event by calling timeRemainingUnbind() in the close events of your modal to prevent memory leaks:
$scope.modalOptions.ok = function (result) {
timeRemainingUnbind();
$modalInstance.close(result);
};
$scope.modalOptions.close = function (result) {
timeRemainingUnbind();
$modalInstance.dismiss('cancel');
};
See my working forked plunker here
I'm encountering a weird issue on IE9. What I'm trying to do is opening a popup window from my page and attaching the scope of the page to the window object like:
angularApp.controller('ParentCtrl', function($scope, $window) {
$window.parentScope = $scope;
$window.open(url, popupName, params);
});
In my popup/child controller I'm using some property from the parent scope to populate data. Then in my popup page I have some controls which lets a user to pick certain persons from a multi select box. After the user clicks on Assign persons from the popup page I'm calling a method in my parent page like:
childApp.controller('ChildCtrl', function($scope, $window) {
$window.opener.parentScope.updatePersons($scope.personInfo);
}
The method updatePersons is defined in the ParentCtrl as:
$scope.updatePersons = function(personInfo){
$scope.$apply(function() {
$scope.personInfo = personInfo;
});
};
Until now everything works fine and the updated personInfo array is copied from popup to the parent page. Now when I again open the popup page from a button on the parent page the value of $scope.personInfo in ParentCtrl mysteriously gets set to undefined.
The personInfo array on the Parent page is used in a ng-repeat to show a tabular list of persons with their attributes and in IE9 I see undefined in all the columns of the table.
Apparently, Safari, Firefox and Chrome all work flawlessly and don't set the value of this scope variable to undefined when the popup page is clicked again. I've tried looking for a solution to this problem but have failed. I've put id="ng-app" at the root of my application and followed some IE caveats mentioned on angularJs project page but w/o any success. I would really appreciate any help from the community as it's a show stopper for my application.
I created a plunker to demo what I'm trying to achieve. Somehow the child page doesn't show parent scope values and the child can't update parent scope but that's working in my application.
http://plnkr.co/edit/E5pzzYOCMXHTK1huCAF5?p=preview
Thanks in advance for the help!
The plnkr example wasn't working for me in Chrome or IE.
It, for some reason, was trying to put duplicate values into the repeater. Tracking the repeat items by index fixed the issues I had with the example.
<div ng-repeat="person in personInfo track by $index">
Edit: To clarify - the plnkr worked in both Chrome and an emulated IE9 (IE11 emulating IE9) session after adding the track by.
I ended up solving the issue by using angular.copy to send data from parent to popup and then while updating personInfo from popup to parent. It worked to some extent so much so that the personInfo object wasn't getting set to undefined when i clicked on the popup again.
But I encountered another problem when I was receiving the updated data from popup to parent. My personInfo array had 2 nested JS arrays within it and when I was receiving the array from popup everything was fine but when I assigned it to my parent scope using $scope.personInfo = angular.copy(personInfo) the two nested JS arrays within $scope.personInfo converted to JS objects and length method didn't work on them.
The only way I could solve this was to iterate over each person object and then check if any of the arrays had length property undefined and then use jQuery's map function to convert those objects back into JS arrays. It's a silly hack but if anyone knows why is it happening on IE and what's the best angular way to do it that would be awesome!
I still don't get it why IE is treating the array differently when all other browsers seem to work fine. Also, why do I have to use angular.copy everywhere to make IE happy!!!
Thanks!