Update $scope and reflect in view - authentication

In my angularjs app I am rendering my "navigation-bar" div for every page.
After user logs in and redirect to details page, then I want I am updating $scope which is not reflected into the view.
To reflect the change of $scope I am calling $digest by using $scope.$apply(). Seems it not updating and the $scope update still not reflecting in my view.
My code looks like below:
CONTROLLER:
function NavCtrl($scope){
//as isAuth false
$scope.showLogin = true;
$scope.showLogout = false;
}
function ProductDetails($scope){
//as isAuth true
$scope.showLogin = false;
$scope.showLogout = true;
if (!$scope.$$phase) {
//$digest or $apply to reflect update of scope update
$scope.$apply();
}
}
VIEW:
<div id="navigation-bar" ng-controller="NavCtrl">
<li ng-show="showLogin">Login</li>
<li ng-show="showLogout">Logout</li>
</div>
What I am doing wrong? Am I missing any point? By the way I went through other questions like AngularJS $scope updates not reflected in the view but it doesn't help in my case.

The main problem is that you are using two different controllers. Each controller has it's own scope (i.e. the scope is not global), so when you change showLogin in your ProductDetails it is only the local scope that changes, not that of NavCtrl.
You can either rewrite it to use one controller, or pass data between your controllers, there are several ways to do that:
You can set data on the $rootScope . The rootScope is a global scope that all controllers can access and that you can always use in templates. I recommend keeping the rootScope to a minimum since the rootScope is shared everywhere and you might end up with a big mess of variables in it.
You can pass data through a service. Only one instance of each service is ever created, so different controllers can access the same service and get the same data.
You can use .$broadcast to send a signal to any child controller. This is probably also best kept to a minimum, a lot of broadcasts will sooner or later slow you down (although the limit should be high).

The problem is you should be using a single controller for this. Here is an example of a controller that will display the correct menu depending on his connected scope variable. The value is set with AJAX or somewhere else in your code.
CONTROLLER:
function NavCtrl($scope, $rootScope){
$scope.showLogin = true;
$scope.showLogout = false;
$rootScope.$watch("connected", function() {
if ($rootScope.connected) {
$scope.showLogin = false;
$scope.showLogin = true;
} else {
$scope.showLogin = true;
$scope.showLogin = false;
}
});
}
VIEW:
<div id="navigation-bar" ng-controller="NavCtrl">
<li ng-show="showLogin">Login</li>
<li ng-show="showLogout">Logout</li>
</div>
Connect me

I suggest an AuthUserService to keep track of login state, and injecting that service into your NavCtrl and any other controllers that need to know about the user:
.factory('AuthUserService', ['$http','$q',
function($http, $q) {
var user = {};
return {
user: function() {
return user;
},
login: function(user_credentials) {
$http.post('/login', user_credentials).then(
function(response) {
angular.copy(response.data, user);
...
});
},
logout: function() { ...
}
}
}])
.controller('NavCtrl', ['AuthUserService','$scope',
function(AuthUserService, $scope) {
$scope.user = AuthUserService.user();
$scope.$watch('user.name', function(name) {
if(name) {
$scope.loggedIn = true;
} else {
$scope.loggedIn = false;
}
});
}])
<div ng-controller="NavCtrl">
<li ng-hide="loggedIn">Login</li>
<li ng-show="loggedIn">Logout</li>
</div>

Related

Pop Up message when deleting contact

If you delete partner from form view. you go actions> delete and then you get the message "Do you really want to delete this records?"
This is original method
on_button_delete: function() {
var self = this;
var def = $.Deferred();
this.has_been_loaded.done(function() {
if (self.datarecord.id && confirm(_t("Do you really want to delete this recordss?"))) {
self.dataset.unlink([self.datarecord.id]).done(function() {
if (self.dataset.size()) {
self.execute_pager_action('next');
} else {
self.do_action('history_back');
}
def.resolve();
});
} else {
$.async_when().done(function () {
def.reject();
});
}
});
return def.promise();
but if you add a contact to partner, when you want to delete it(it's in the bottom inside contact & addresses tab) there is no such message, so I want to make the same popup work when I delete a contact from a partner. But as have 0 knowledge in JS I need your help guys.
That could be done using the following js code:
odoo.define('x2many_kanban_delete_confirm', function (require) {
"use strict";
var KanbanView = require('web_kanban.KanbanView');
KanbanView.include({
init: function (parent, dataset, view_id, options) {
this._super(parent, dataset, view_id, options);
this.options.confirm_on_delete = true;
}
});
});
Just load it into your Odoo and it will allow you to confirm the delete in the kanban view of an x2many field that are the ones that don't allow the confirm check before delete.
If you wanna you could check for an specific model, using this condition:
if(this.model == 'res.partner') {
this.options.confirm_on_delete = true;
}
But I don't find it necessary as it won't break anything

Aurelia, check when DOM is compiled?

How to check when DOM is compiled and inserted from Aurelia repeat cycle when the model is updated?
I have the following html:
<div clas="parent">
<div class="list-group">
<a repeat.for="$item of treeData">${$item.label}</a>
</div>
</div>
Here I need to know when all <a> tags are listed in the DOM, in order to run jquery scroll plugin on the parent <div> container.
At first load, I do that from the attached() method and all is fine.
When I update the treeData model from a listener, and try to update the jquery scroll plugin, it looks that the DOM is not compiled, so my scroll plugin can not update properly.
If I put timeout with some minimum value like 200ms it works, but I don't think it is a reliable workaround.
So is there a way to solve that?
Thanks!
My View Model:
#customElement('tree-view')
#inject(Element, ViewResources, BindingEngine)
export class TreeView {
#bindable data = [];
#bindable filterFunc = null;
#bindable filter = false;
#bindable selectedItem;
constructor(element, viewResources, bindingEngine) {
this.element = element;
this.viewResources = viewResources;
this.bindingEngine = bindingEngine;
}
bind(bindingContext, overrideContext) {
this.dataPropertySubscription = this.bindingEngine
.propertyObserver(this, 'data')
.subscribe((newItems, oldItems) => {
this.dataCollectionSubscription.dispose();
this._subscribeToDataCollectionChanges();
this.refresh();
});
this.refresh();
if (this.filter === true) {
this.filterChanged(this.filter);
}
if (this.selectedItem) {
this.selectedItemChanged(this.selectedItem);
}
}
attached() {
$(this.element).perfectScrollbar();
}
refresh() {
this.treeData = processData(this.data, this.filterFunc);
this.listItemMap = new WeakMap();
this.treeData.forEach(li => this.listItemMap.set(li.item, li));
this.filterChanged(this.filter);
$(this.element).perfectScrollbar('update');
}
This is only part of the code, but most valuable I think.
I attach the jq plugin in attached function and try to update it in refresh function. In general I have listener that track model in other view, which then update that one without triggering bind method.
An approach would be to use something called window.requestAnimationFrame (https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame).
In your view-model, when you modify your treeData array, try calling
window.requestAnimationFrame(()=>{
$.fn.somePlugin();
});
Haven't tested this out, but based off what you're telling me, this might do what you need.
You could push your code onto the microTaskQueue, which will schedule your function to be executed on the next event loop. For instance:
import { TaskQueue } from 'aurelia-task-queue';
//...
#inject(Element, ViewResources, BindingEngine, TaskQueue)
export class TreeView {
constructor(element, viewResources, bindingEngine, taskQueue) {
this.element = element;
this.viewResources = viewResources;
this.bindingEngine = bindingEngine;
this.taskQueue = taskQueue;
}
refresh() {
this.treeData = processData(this.data, this.filterFunc);
this.listItemMap = new WeakMap();
this.treeData.forEach(li => this.listItemMap.set(li.item, li));
this.filterChanged(this.filter);
// queue another task, which will execute after the tasks queued above ^^^
this.taskQueue.queueMicroTask(() => {
$(this.element).perfectScrollbar('update');
});
}
}

Mount not working inside controller vm

I'm honestly not sure why this is not working. Seems to be a pretty standard operation. It is not mounting the component, is not throwing an error, and not running the function directly after it. All happing in cfg.AddToCart.vm.addToCart()
cfg.AddToCart = {
vm: {
init() {
return;
},
addToCart() {
let parent = document.getElementById('atc-error');
let errEl = document.getElementById('atc-error-component');
if(cfg.state.selections.SIZE) {
m.mount(errEl, null);
} else {
let component = new cfg.selectComponent(cfg.Options, cfg.optionsView);
m.mount(errEl, component);
cfg.util.toggleSlide(parent);
}
}
},
controller() {
cfg.AddToCart.vm.init();
}
};
cfg.AddToCart.view = function() {
return <div id="add-to-cart-container">
<div id="atc-error">
<span>Select a size and add to cart again.</span>
<div id="atc-error-component"></div>
</div>
<div class="small-12 columns">
<button class="large button alert"
onclick={() => {
this.vm.addToCart();
}}>
Add To Cart
</button>
</div>
</div>;
};
We use the new cfg.selectComponent(cfg.Options, cfg.optionsView) component multiple times throughout the application, so it is not an error with that. #atc-error is set to display:none, but that also doesn't seem to be the problem. This is not the only conditional mount in the application, so that is why I'm a bit stumped.
from looking at the way you've structured your code it strikes me you're missing out on a lot of Mithril's benefits. In particular:
If your 'vm' is indistinguishable from the controller, then you don't need to create and manage a whole separate object for that. Especially when you're using methods to control local component state, that is the job of the controller. The controller exposes an object to the view — this should be considered the 'vm' to that extent. Having a separate object to hold model state is useful when the state is relevant outside of the component instance: you already have this in your cfg.state, so in this scenario the vm is redundant.
Mithril views have a config method which exposes the real DOM element after every draw. You don't need to store references to view elements since you can do it here. This is a large part of what makes virtual DOM libraries so appealing: the view is clever, and you can introduce view-specific logic in them directly.
Components can be called directly from within the view, and the view can use conditional logic to determine whether or not to call them. m.mount is only necessary to initialise a Mithril application and define 'top level' components; from within Mithril code you can invoke nested components via m function directly.
A couple of other misunderstandings:
The controller executes before the view is rendered (and once it's executed, the properties it initialises are exposed to your view function as the first argument), so you can't access elements created by the view when the controller initialises.
The init function in the vm serves no purpose.
Here's a rewrite of your code that takes the above into account. I used plain Mithril instead of MSX to avoid compilation, but you could easily convert it back:
// Determine what your external dependencies are
const { state, selectComponent } = cfg
// Define the component
const AddToCart = {
// No need for a separate VM: it is identical in purpose & function to the controller
controller : function(){
// No need to store element references in the model: those are the view's concern.
// Keep the VM / ctrl size to a minimum by only using it to deal with state
this.addToCart = () => {
if( state.selections.SIZE )
this.showSize = false
else {
this.showSize = true
this.slideToErr = true
}
}
},
view : ctrl =>
m( '#add-to-cart-container',
m( '#atc-error', {
// Config exposes the element and runs after every draw.
config : el => {
// Observe state, and affect the view accordingly:
if( ctrl.slideToErr ){
el.scrollIntoView()
// Reset the state flag
ctrl.slideToErr = false
}
}
},
m( 'span', 'Select a size and add to cart again.' ),
// This is an and condition, ie 'if A, then B
ctrl.showSize
// This is how you invoke a component from within a view
&& m( selectComponent )
),
m( '.small-12 columns',
m( 'button.large button alert', {
onclick : () =>
ctrl.addToCart();
},
'Add To Cart'
)
)
)
}
Worked by changing it to this pattern:
cfg.AddToCart = {
vm: {
init() {
this.errorComponent = m.prop();
},
addToCart() {
let parent = document.getElementById('atc-error');
let errEl = document.getElementById('atc-error-component');
if(cfg.state.selections.SIZE) {
cfg.util.toggleSlide(parent);
setTimeout(() => {
this.errorComponent(null);
}, 400);
} else {
let component = new cfg.selectComponent(cfg.Options, cfg.optionsView);
this.errorComponent(component);
setTimeout(() => {
cfg.util.toggleSlide(parent);
}, 100);
}
}
},
controller() {
cfg.AddToCart.vm.init();
}
};
cfg.AddToCart.view = function() {
return <div id="add-to-cart-container">
<div id="atc-error">
<span>Select a size and add to cart again.</span>
<div id="atc-error-component" class="row">
{this.vm.errorComponent() ? m.component(this.vm.errorComponent()) : ''}
</div>
</div>
<div class="small-12 columns">
<button class="large button alert"
onclick={() => {
this.vm.addToCart();
}}>
Add To Cart
</button>
</div>
</div>;
};

Durandal Composition Binding with canDeactivate

I am using Durandal 2.1, and I am having a problem with view composition. I have a view for managing many types of items. I also want a view to manage a subset of those types. So I created a manage view and a managesubset view. The managesubset view just composes the manage view and passes it an array containing the subset of items. This way the user can go to /100/manage or 100/managesubset where managesubset will only allow the user to manage a subset of items. I am using this pattern because I will have multiple different versions of managesubset.
My problem is that the canDeactivate method is not fired when going to managesubset. Is there anyway to fire the canDeactivate and Deactivate lifecycle events when composing?
According to #3 under Activator Lifecycle Callbacks here, I should be able to do this, but I cannot find any good examples.
Code:
manage.js
define(['durandal/app', 'plugins/router'], function (app, router) {
var constructor = function () {
var self = this;
//...variable creation and assignment
//life cycle events
self.activate = function (viewmodel) {
self.recordId(viewmodel.recordId);
self.assignableTypes(viewmodel.assignableTypes);
self.pageHeaderTitle = viewmodel.pageHeaderTitle;
self.pageHeaderIcon = viewmodel.pageHeaderIcon;
};
self.canActivate = function (id) {
var deferred = $.Deferred();
//check if user has access to manage equipment
};
self.canDeactivate = function () {
if (!self.saveSuccessfull() && this.isDirty()) {
return app.showMessage("You have unsaved changes, are you sure you want to leave?", "Unsaved Changes", ["Yes", "No"]);
}
else {
return true;
}
}
};
return constructor;
});
managesubset.js
define([], function () {
var recordId = ko.observable();
var manageRecord = ko.observable();
return {
recordId: recordId,
manageRecord: manageRecord,
activate: function (id) {
recordId(id);
manageRecord({
pageHeaderTitle: 'Manage Subset',
pageHeaderIcon: 'cb-subset',
assignableTypes: [102],
recordId: recordId()
});
},
canActivate: function (id) {
var deferred = $.Deferred();
//check if user has access to manage equipment
}
}
});
managesubset.html
<div data-bind="compose: { model: 'manage', activationData: manageRecord() }"></div>
The activate is called correctly each time. The deactivate and canDeactive are what don't work, and they are never called.

Durandal, get path of the current module

Is there a way in Durandal to get the path of the current module? I'm building a dashboard inside of a SPA and would like to organize my widgets in the same way that durandal does with "FolderWidgetName" and the folder would contain a controller.js and view.html file. I tried using the getView() method in my controller.js file but could never get it to look in the current folder for the view.
getView(){
return "view"; // looks in the "App" folder
return "./view"; // looks in the "App/durandal" folder
return "/view"; // looks in the root of the website
return "dashboard/widgets/htmlviewer/view" //don't want to hard code the path
}
I don't want to hardcode the path inside of the controller
I don't want to override the viewlocator because the rest of the app still functions as a regular durandal spa that uses standard conventions.
You could use define(['module'], function(module) { ... in order to get a hold on the current module. getView() would than allow you to set a specific view or, like in the example below, dynamically switch between multiple views.
define(['module'], function(module) {
var roles = ['default', 'role1', 'role2'];
var role = ko.observable('default');
var modulePath = module.id.substr(0, module.id.lastIndexOf('/') +1);
var getView = ko.computed(function(){
var roleViewMap = {
'default': modulePath + 'index.html',
role1: modulePath + 'role1.html',
role2: modulePath + 'role2.html'
};
this.role = (role() || 'default');
return roleViewMap[this.role];
});
return {
showCodeUrl: true,
roles: roles,
role: role,
getView: getView,
propertyOne: 'This is a databound property from the root context.',
propertyTwo: 'This property demonstrates that binding contexts flow through composed views.',
moduleJSON: ko.toJSON(module)
};
});
Here's a live example http://dfiddle.github.io/dFiddle-1.2/#/view-composition/getView
You can simply bind your setup view to router.activeRoute.name or .url and that should do what you are looking for. If you are trying to write back to the setup viewmodels property when loading you can do that like below.
If you are using the revealing module you need to define the functions and create a module definition list and return it. Example :
define(['durandal/plugins/router', 'view models/setup'],
function(router, setup) {
var myObservable = ko.observable();
function activate() {
setup.currentViewName = router.activeRoute.name;
return refreshData();
}
var refreshData = function () {
myDataService.getSomeData(myObservable);
};
var viewModel = {
activate: activate,
deactivate: deactivate,
canDeactivate: canDeactivate
};
return viewModel;
});
You can also reveal literals, observables and even functions directly while revealing them -
title: ko.observable(true),
desc: "hey!",
canDeactivate: function() { if (title) ? true : false,
Check out durandal's router page for more info on what is available. Also, heads up Durandal 2.0 is switching up the router.
http://durandaljs.com/documentation/Router/
Add an activate function to your viewmodel as follows:
define([],
function() {
var vm = {
//#region Initialization
activate: activate,
//#endregion
};
return vm;
//#region Internal methods
function activate(context) {
var moduleId = context.routeInfo.moduleId;
var hash = context.routeInfo.hash;
}
//#endregion
});