How to extend/overwrite default options in Angular Material $mdDialog.show? - javascript

TL;DR : I need a way to overwrite default options provided my Angular Material (especially on Material Dialog) using providers (like any other angular modules - a random example).
I have been looking for a way to customize defaults options Angular Material Modal but without any usable result.
Like I have used on other plugins/modules this way could be achieved using a provider. Having a look in the core of the material (1.0.8) I was trying to set options using setDefaults method like this (let say I just want to disable backdrop for moment):
app.config(['$mdDialogProvider', function($mdDialogProvider){
console.log($mdDialogProvider);
// ^ $get/addMethod/addPreset/setDefaults
var defaults = {
options: function(){
return {
hasBackdrop: false
}
}
}
$mdDialogProvider.setDefaults(defaults);
}]);
Right now when I am checking the options on onComplete callback :
So as you can see the hasBackdrop option is updated, but the modal is not working anymore so I think I am missing something.
Do you have any idea how the angular defaults could be extended in a proper way?
Thanks
UPDATE : Options object without having .setDefaults active (de initial state)
Note : I have copied from their core transformTemplate and added in my defaults object, but the result is the same. I can see the DOM updated, console has no errors, but the modal is not visible.

When you want to update an existing functionality from a third party library, you should try to use decorator pattern and decorate the service method.
Angular provides a neat way of doing this using decorators on providers while configuring the app: https://docs.angularjs.org/api/auto/service/$provide
$provide.decorator
$provide.decorator(name, decorator);
Register a service decorator with the $injector. A service decorator intercepts the creation of a service, allowing it to override or modify the behavior of the service. The object returned by the decorator may be the original service, or a new service object which replaces or wraps and delegates to the original service.
You can write a decorator for $mdDialogProvider to extend the functionality of the .show method and pass it the extended options object like shown below:
.config(function ($provide) {
// Decorate the $mdDialog service using $provide.decorator
$provide.decorator("$mdDialog", function ($delegate) {
// Get a handle of the show method
var methodHandle = $delegate.show;
function decorateDialogShow () {
var args = angular.extend({}, arguments[0], { hasBackdrop: false })
return methodHandle(args);
}
$delegate.show = decorateDialogShow;
return $delegate;
});
});
I have created a codepen with a working example with { hasBackdrop: false } so that backdrop is not shown on calling $mdDialog.show(): http://codepen.io/addi90/pen/RaXqRx

Please find the codepen with the demo here: http://codepen.io/shershen08/pen/vGoQZd?editors=1010
This how service will look:
var dialogFactory = function($mdDialog) {
var options = {};
return {
create: function(conf) {
var preset = $mdDialog.alert()._options; //get defaults
var newOptions = angular.extend(preset, conf, options);//extend with yours
$mdDialog.show(newOptions);
},
//toggle various props
setProp: function(prop, val) {
options[prop] = val;
}
};
};
and in the controller you can use it like this:
$scope.toggleBackdrop = function() {
$scope.backdrop = !$scope.backdrop;
//here we change the state of the service internal var
dialogService.setProp('hasBackdrop', $scope.backdrop);
};
$scope.showDialogViaService = function(ev) {
//here we fill in the needed params of the modal and pass to the service
var obj = {
'title': 'title',
'content': 'content',
'ok':'Ok!'
};
dialogService.create(obj);
}

Related

AngularJS: How to set one state as a global parent in UI-Router (to use modal as common parts)

In $stateProvider#state(), I define it as follows for Bootrtrap-ui modal using UI-Router. reference
var state = {
name: 'modala',
parent: 'home',
onEnter: function($modal, $state) {
modalInstance = $modal.open({
templateUrl: 'modala.html',
controller: 'modalaCtrl',
controllerAs: 'modal'
});
modalInstance.result['finaly'](function() {
modalInstance = null;
if ($state.$current.name === 'modala') {
$state.go('^');
}
});
},
onExit: function() {
if (modalInstance) {
modalInstance.close();
}
}
};
I want to use 'modala' any place other than home.
I do not want to create a lot of definitions of 'modala'.
Is there a method to accept it, and to set what is necessary in parent?
add explanation
No good solutions
Don't set parent in modal state.
result: when open modal window, parent isn't displayed.
pattern 1 example
modal state name is ${parent.name}.modal format.
result: It's work. but, About one modal window, it is necessary to define many states. and, It is necessary to add a state whenever I add an parent who call modal.
pattern 2 exapmle
define the modal state every parent
result:same as pattern 2.
pattern 3 exapmle
There is updated plunker (the second try from the question)
What we would see in action, is different use of decorator, as in detail described in my previous post
So, firstly, let's have few states e.g. 'home', 'page1', 'page2' ... and we would like for all of them introduce the child state - with modal functionality. That modal features are expected to be the same across all child states - but these will belong to different parents.
To make it happen we can use state definition like this:
.config(function($stateProvider) {
// we just define empty child state for each existing parent
$stateProvider.state('home.generalModal', {});
$stateProvider.state('page1.generalModal', {});
$stateProvider.state('page2.generalModal', {});
})
And that could be enough, if we will hijack the decorator this way:
.config(['$stateProvider',
function($stateProvider) {
var endsWith = function(stringToBesearched, valueToBeFound) {
return stringToBesearched.slice(-valueToBeFound.length) === valueToBeFound;
}
$stateProvider.decorator('data', function (state, dataBuilder) {
// this is original, by configuration created parent
var data = dataBuilder(state);
// we can define where we do not want to change that default, configured
// in our case, we extend every state which ends with .generalModal
var skipState = !endsWith(state.name, ".generalModal")
// and return taht default
if(skipState)
{
return data;
}
// the modal instance per this state instance
var modalInstance;
// definition of the onEnter
var onEnter = function($modal, $state) {
console.log("Entering state: " + $state.current.name)
modalInstance = $modal.open({
templateUrl:'modal.html',
controller: 'modalCtrl',
controllerAs: 'modalCtrl'
});
modalInstance.result['finally'](function() {
modalInstance = null;
if(endsWith($state.$current.name, ".generalModal")) {
$state.go('^');
}
});
};
// definition of the onExit
var onExit = function($state) {
console.log("Exiting state: " + $state.current.name)
if (modalInstance) {
modalInstance.close();
}
};
// extend state with both of them
state.self.onEnter = onEnter;
state.self.onExit = onExit;
return data;
});
}])
We extended every child state which name endsWith ".generalModal" with onExit and onEnter. That's all. At one place...
Check it in action here
All you have to do is add the property parent: modala to all child states that you define in order to set a parent state.
There is a solution built inside of the UI-Router, called state decorator
decorator(name, func)
This way, we can create an aspect, which will be injected into any state we want later. That aspect, could drive some common, global setting. E.g. parent or views...
Check an example here: How to decorate current state resolve function in UI-Router? Current function isn't invoked, small cite:
Allows you to extend (carefully) or override (at your own peril) the stateBuilder object used internally by $stateProvider. This can be used to add custom functionality to ui-router, for example inferring templateUrl based on the state name...
There is a working example for our scenario
What we would need, is the decorator definition, like this:
.config(['$stateProvider',
function($stateProvider) {
$stateProvider.decorator('parent', function (state, parent) {
// this is original, by configuration created parent
var result = parent(state);
// we can define where we do not want to change that default, configured
var skipState = state.name === "home"
|| state.name === "modala"
// child already has that in parent
|| state.name.indexOf(".") > 0 ;
// and return taht default
if(skipState)
{
return result;
}
// FINALLY - HERE
// we want to change parent, or even introduce new
state.parent = 'modala';
// and rebuild it
result = parent(state);
// new parent in place
return result;
});
}])
Check that in action here
I was wanting the solution to this too. I eventually worked it out.
See my stackoverflow answer / question

How to create an empty $resource when using the factory pattern

I am using angular $resource in a factory pattern, where it is injected, so I only have to create the templates once and in one place. This works.
I can not seem to find any documentation on how to create a new resource object. This creates conditional branching when it is time to save, as I do not have an object to call $save() on.
For example: imagine I have this resource:
myService.factory('myWidget', [ '$resource',
function($resource){
return $resource('/my/widget/:id', {
[...]
So my controller can easily get access to myWidget thusly:
function( $scope, ... myWidget ) {
$scope.widget = myWidget.get({id: 'myId'});
$scope.save = function() {
$scope.widget.$save(); // plus progress dialog error handling etc
};
}
Which is very clean and awesome and as it should be. However, if I want to create a new one, I need conditional code both on create and save.
function( $scope, ... myWidget, mode ) {
if (mode === 'create') {
$scope.widget = {
id: 'myNewId',
property: <lots and lots of properties>
};
}
else {
$scope.widget = myWidget.get({id: 'myId'});
}
$scope.save = function() {
$scope.widget.$save(); // Not a $resource; $save() does not exist
};
}
Obviously, I can put conditional code in save(), or I can pass the widget as a parameter, as myWidget.save($scope.widget), but that seems lame. Is there no easy way to simply create a new $resource from the factory? IE:
if (mode === 'create') {
$scope.widget = myWidget.new({
id: 'myNewId',
property: <lots and lots of properties>
});
}
This would be functionally equivalent to :
if (mode === 'create') {
$scope.widget = $resource('/my/widget/:id');
But obviously without duplicating the resource code in the factory.
Surely there is some easy syntax for doing this. Yes?
I am using AngularJS 1.3. I really hope this is a stupid question as it seems like something that is an obvious use case. Even Backbone has a way to create a new REST-backed object with default values. :) Thanks much.
(Maybe the question should be "how do I create an object using the angularjs factory, as if I were the injector?")
You need to be creating a new widget:
if (mode === 'create') {
$scope.widget = new myWidget();
$scope.widget = {
id: 'myNewId',
property: <lots and lots of properties>
};
}

How to replace jquery with the mithril equivalent?

Something like :
peer.on('open', function(id){ // this is a non jquery event listener
$('#pid').text(id);
});
With something like...this is not correct:
peer.on('open', function(id){
m('#pid',[id])
});
Is this even the right approach? Should I be establishing a controller and model before I attempt to convert from jquery?
More details:
I am trying to rewrite the connect function in the PeerJS example: https://github.com/peers/peerjs/blob/master/examples/chat.html
If your event listener is something like websockets, then the event happens outside of Mithril, which means you need to manage redrawing yourself. This is what you'll need to do:
Store your data in an independent model
Use that model when rendering your Mithril view
On the open event, update your model, then call m.redraw()
Conceptual example:
var myModel = { id: 'blank' }
var MyComponent = {
view: function () {
return m('#pid', myModel.id)
}
}
m.mount(document.getElementById('app'), MyComponent)
// This happens outside mithril, so you need to redraw yourself
peer.on('open', function(id) {
myModel.id = id
m.redraw()
})
In Mithril, you should not try to touch the DOM directly. Your event handler should modify the View-Model's state, which should be accessed in your View method. If you post more code, I could give a more detailed explanation of how it pieces together.
Here is a bare-bones example that shows the data flowing through Mithril. Your situation will need to be more complicated but I'm not currently able to parse through all of that peer.js code.
http://codepen.io/anon/pen/eNBeQL?editors=001
var demo = {};
//define the view-model
demo.vm = {
init: function() {
//a running list of todos
demo.vm.description = m.prop('');
//adds a todo to the list, and clears the description field for user convenience
demo.vm.set = function(description) {
if (description) {
demo.vm.description(description);
}
};
}
};
//simple controller
demo.controller = function() {
demo.vm.init()
};
//here's the view
demo.view = function() {
return m("html", [
m("body", [
m("button", {onclick: demo.vm.set.bind(demo.vm, "This is set from the handler")}, "Set the description"),
m("div", demo.vm.description())
])
]);
};
//initialize the application
m.module(document, demo);
Notice that the button is calling a method on the View-Model (set), which is setting the value of a property (vm.description). This causes the View to re-render, and the div to show the new value (m("div", demo.vm.description())).

Object oriented approach with AngularJS

It seems that Angular does not provide a built-in solution to define class instances with properties and methods and that it's up the developer to build this.
What is the best practice to do this in your opinion?
How to you link this with the backend?
Some of the tips I have gathered use factory services and named functions.
Sources :
Tuto 1
Tuto 2
Thanks for your insights
I think that the closest structure to an Object it's probably a factory, for several reasons:
Basic Syntax:
.factory('myFactory', function (anInjectable) {
// This can be seen as a private function, since cannot
// be accessed from outside of the factory
var privateFunction = function (data) {
// do something
return data
}
// Here you can have some logic that will be run when
// you instantiate the factory
var somethingUseful = anInjectable.get()
var newThing = privateFunction(somethingUseful)
// Here starts your public APIs (public methods)
return {
iAmTrue: function () {
return true
},
iAmFalse: function () {
return false
},
iAmConfused: function () {
return null
}
}
})
And then you can use it like a standard Object:
var obj = new myFactory()
// This will of course print 'true'
console.log( obj.iAmTrue() )
Hope this helps, I perfectly know that the first impact with angular modules can be pretty intense...
You would use an angular service.
All angular services are singletons and can be injected into any controller.
Ideally you would keep only binding/actions on html in your controller and the rest of the logic would be in your service.
Hope this helps.
I got idea by evaluating this library : https://github.com/FacultyCreative/ngActiveResource
However this library assumes strict rest so I it wasn't work for me. What did work for is this:
I created base Model
var app = angular.module('app', []);
app .factory('Model', function(){
var _cache = {}; // holding existing instances
function Model() {
var _primaryKey = 'ID',
_this = this;
_this.new = function(data) {
// Here is factory for creating instances or
// extending existing ones with data provided
}
}
return Model;
});
Than I took simple function extensions "inherits"
Function.prototype.inherits = function (base) {
var _constructor;
_constructor = this;
return _constructor = base.apply(_constructor);
};
and now I cam creating my models like this
app.factory('Blog', [
'Model',
'$http',
function(Model, $http) {
function Blog() {
// my custom properties and computations goes here
Object.defineProperty(this, 'MyComputed' , {
get: function() { return this.Prop1 + this.Prop2 }
});
}
// Set blog to inherits model
Blog.inherits(Model);
// My crud operations
Blog.get = function(id) {
return $http.get('/some/url', {params: {id:id}}).then(function(response) {
return Blog.new(response.data);
});
}
return Blog;
}
]);
Finally, using it in controller
app.controller('MyCtrl', [
'$scope', 'Blog',
function($scope, Blog) {
Blog.get(...).then(function(blog) {
$scope.blog = blog;
});
}
])
Now, there is much more in our Model and extensions but this would be a main principle. I am not claiming this is best approach but I am working pretty big app and it really works great for me.
NOTE: Please note that I typed this code here and could be some errors but main principle is here.
As my question does not really reflect the issue I was facing, I'll just post my approach for the sake of it :
As Domokun put it, rule of thumb is to decouple front and back. But as I am only building a prototype and managing both ends, I would like to keep things in only one place and let the rest of the application use the central information as a service.
What I want to do here is to build a form through ng-repeat containing the model fields and most importantly how to display information in the form (e.g. 'Last name' instead of 'lastname')
So as I started working around with mongoose models here's what I have managed to do :
Firstly, it is possible to pass the mongoose schema of a model from node side to angular side with an app.get request with the following response :
res.send(mongoose.model('resources').schema.paths);
this spitts out an object containing all fields of the 'resources' collection. On top of that I included some additional information in the model like this :
var resourceSchema = new Schema({
_id: { type: Number },
firstname: { type: String, display:'First name' },
lastname: { type: String, display:'Last name' }
});
mongoose.model('resources', resourceSchema);
So basically I can retrieve this symmetrically on angular side and I have all I need to map the fields and display them nicely. It seems I can also describe the validation but I'm not there yet.
Any constructive feedback on this approach (whether it is valid or totally heretic) is appreciated.

Exporting methods from an angular factory or directive to use later

I have developed a web application using Angular.js (It's my first). The application features a collection of interactive graphics (seat maps); so I created a module to handle the Raphael stuff, including a directive, like so:
angular.module('raphael', [])
.factory('fillData', function() {
return function(paper, data) {
var canvas = $(paper.canvas);
// Do fill the data and more ...
canvas.on('click', '[id]', function(e) {
this.classList.toggle('selected');
});
};
})
.directive('raphael', ['fillData',
function(fillData) {
return {
scope: {
raphael : '&',
seatData: '&'
},
link: function(scope, element, attrs) {
var paper = null;
var updateSeatData = function() {
if(scope.seatData()) fillData(paper, scope.seatData());
};
scope.$watch(scope.raphael, function() {
element.empty();
paper = new Raphael(element[0], '100%', '100%');
paper.add(scope.raphael());
updateSeatData();
});
scope.$watch(scope.seatData, function() {
updateSeatData();
});
}
};
}
]);
Everything works fine, until it get to the point where we need to interact with the vector in another level. Let's say, getting a count of selected seats, or deselecting all (triggered by some random element in the document).
I don't seem to be able to find a reasonable way of implementing it.
What do you suggest?
Is there any other approach to using a second library inside angular?
From what I understand you want to have directive which have certain internal state but you would like to access it's state from outside (other directive, service, etc.).
If so, then it seems that you could use service as state holder. In such case your directive will not hold state but it will be accessing it.
What do you mean by a reasonable way of implementing it? It looks good, although I would prefer to bind to the attribute seatData instead of passing function like
scope: {
seatData: '='
}
And then watch it
scope.$watch('seatData', function() {
fillData(paper, scope.seatData);
});
Is this you issue or I haven't understood it?
OK, here is the solution I came up with; I accessed the parent scope and put essential methods there.
Adding this line to the fillData factory:
return {
deselectAll: function() { ... }
};
And changed updateSeatData method to:
var updateSeatData = function() {
if(scope.seatData) {
var result = fillData(paper, scope.seatData[scope.level]);
angular.extend(scope.$parent, result);
}
};
p.s. Still open to hearing moreā€¦

Categories

Resources