I'm new at mithril.js. I have a div, I want to add class "invalid" if ctrl.invalid()==true, and "hidden" if ctrl.hidden()==true.
If I use m('div', {class: ctrl.invalid() ? 'invalid' : '', class: ctrl.hidden()? 'hidden' : ''}), they override each other.
I can use m('div', {class: [ctrl.invalid()?'invalid':'', ctrl.focused()?'focused':''].join(' ')}), and it'll work, but it looks messy.
Is there an elegant solution for this? Thanks.
I recommend you to use classnames - a simple utility for that. You can define your classes in a nice way and it will merge everything for you. In your case it will be:
const myMergedClasses = classNames({
invalid: ctrl.invalid(),
focused: ctrl.focused()
});
m('div', { class: myMergedClasses })
Beautiful?!
Very late to the game, but as an inspiration for others ending up here, I often do something like the following, just because it is:
simple to implement
easy to extend
easy to understand
view(): {
const classes =
`${ctrl.invalid() ? '.invalid' : ''}` +
`${ctrl.hidden()? '.hidden' : ''}`;
return m(`div${classes}`);
}
You can add a helper method to your Mithril component:
const myComponent = {
css() {
// Add some logic
return 'class1 class2';
},
view() {
return m('div', { class: this.css() });
},
};
Or to the controller:
const ctrl = {
css() {
// Add some logic
return 'class3';
},
};
const myComponent = {
view() {
return m('div', { class: ctrl.css() });
},
};
Choose whichever suits your case better.
You can also use the classnames utility, as suggested by Ross Khanas in his answer:
const myComponent = {
css() {
return classNames({
invalid: ctrl.invalid(),
focused: ctrl.focused(),
});
},
view() {
return m('div', { class: this.css() });
},
};
Or:
const ctrl = {
css() {
return classNames({
invalid: this.invalid(),
focused: this.focused(),
});
},
invalid() { /* ... */ },
focused() { /* ... */ },
};
const myComponent = {
view() {
return m('div', { class: ctrl.css() });
},
};
Related
I am trying to update data property through computed property and found that it is impossible to set the value but I can use get/set to assign value in data property. Please see my example first.
data () {
return {
title: '',
color: null
}
},
computed: {
isTitle: {
get () {
return this.title
},
set () {
console.log('how can I come to this line?')
this.title = 'update title example'
}
},
isTitle() {
this.color = 'red'
return 'update title example'
}
},
mounted () {
this.getAccessToTitle()
},
methods: {
getAccessToTitle () {
if (isTitle) {
this.color = 'red'
}
},
example looks little bit weird but what I wanted to ask is..
when getAccessToTitle() is called through mounted, I assume, isTitle's set() should update the title in data property isn't it? I am not sure how can I use set in computed property when I call isTitle through methods but not template(I saw many examples that use template to call computed like https://vuejs.org/guide/essentials/computed.html#writable-computed but it is not what I am looking for!)
Thank you
this is what I wanted to do originally. update color in data and return title in isTitle. Tt works but was told that it is bad way to use computed so I added get/set
data () {
return {
title: '',
color: null
}
},
computed: {
isTitle() {
this.color = 'red' <---
return 'update title example' <---
}
},
mounted () {
this.getAccessToTitle()
},
methods: {
getAccessToTitle () {
if (isTitle) {
isColor(this.color)
}
},
isColor(val) {
// do something...
}
I tried my best to write a custom directive in apollo server express to validate if an input type field of type [Int] does not have more than max length but do not know if its the right way to do. Appreciate if somebody could help me correct any mistakes in the code below.
// schema.js
directive #listLength(max: Int) on INPUT_FIELD_DEFINITION
input FiltersInput {
filters: Filters
}
input Filters {
keys: [Int] #listLength(max: 10000)
}
// Custom directive
const { SchemaDirectiveVisitor } = require('apollo-server-express');
import {
GraphQLList,
GraphQLScalarType,
GraphQLInt,
Kind,
DirectiveLocation,
GraphQLDirective
} from "graphql";
export class ListLengthDirective extends SchemaDirectiveVisitor {
static getDirectiveDeclaration(directiveName) {
return new GraphQLDirective({
name: directiveName,
locations: [DirectiveLocation.INPUT_FIELD_DEFINITION],
args: {
max: { type: GraphQLInt },
}
});
}
// Replace field.type with a custom GraphQLScalarType that enforces the
// length restriction.
wrapType(field) {
const fieldName = field.astNode.name.value;
const { type } = field;
if (field.type instanceof GraphQLList) {
field.type = new LimitedLengthType(fieldName, type, this.args.max);
} else {
throw new Error(`Not a scalar type: ${field.type}`);
}
}
visitInputFieldDefinition(field) {
this.wrapType(field);
}
}
class LimitedLengthType extends GraphQLScalarType {
constructor(name, type, maxLength) {
super({
name,
serialize(value) {
return type.serialize(value);
},
parseValue(value) {
value = type.serialize(value);
return type.parseValue(value);
},
parseLiteral(ast) {
switch (ast.kind) {
case Kind.LIST:
if (ast.values.length > maxLength) {
throw {
code: 400,
message: `'${name}' parameter cannot extend ${maxLength} values`,
};
}
const arrayOfInts = ast.values.map(valueObj => parseInt(valueObj['value']));
return arrayOfInts;
}
throw new Error('ast kind should be Int of ListValue')
},
});
}
}
Does this look right?
Thanks
I have to say HttpClient Observables, subscriptions etc are pretty hard/time consuming to get right.
I have been working on a problem for a while now and tearing my hair out. I have a service that I need to be able to perform a mapping function on.
loadAllSummary(organisationId: number) {
return this.http.get('/api/aircrafts/organisations/' + organisationId)
.pipe(
map(data => data.forEach(datum => {
console.log('why am i not getting here! ' + JSON.stringify(data));
return this.mapToSummary(datum);
}))
);
}
with the mapToSummary() method:
private mapToSummary(aircraft: Aircraft): IAircraftSummary {
const lastDate: Date = new Date(Math.max.apply(null, aircraft.workorders.map(function(e) {
return new Date(e.date);
})));
return new AircraftSummary({
lastWork: lastDate,
rego: aircraft.registration,
make: aircraft.make,
model: aircraft.model,
contact: (aircraft.owner.type.endsWith('primary')) ? aircraft.owner.principal : aircraft.operator.principal,
phone: (aircraft.owner.type.endsWith('primary')) ? aircraft.owner.contact.phone : aircraft.operator.contact.phone
});
}
Now, I need these summaries as input data to a view, so I borrowed code from the interwebs and created this ResolverService:
#Injectable()
export class AircraftsResolverService implements Resolve<IAircraftSummary[]> {
constructor(private service: AircraftService,
private router: Router) { }
resolve(route: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<IAircraftSummary[]> {
console.log('called AircraftsResolverService')
const id = route.params['id'];
if (isNaN(+id)) {
console.log(`Organisation id was not a number: ${id}`);
this.router.navigate(['/login']);
return Observable.of(null);
}
return this.service.loadAllSummary(+id)
.map(summaries => {
console.log(summaries)
if (summaries) {
return summaries;
}
console.log(`Summaries were not found: ${id}`);
this.router.navigate(['/organisations/', +id]);
return null;
})
.catch(error => {
console.log(`Retrieval error: ${error}`);
this.router.navigate(['/organisations/', +id]);
return Observable.of(null);
});
}
}
Which I then refer to in the ngOnInit call...
ngOnInit() {
this.currentUser = this.authenticationService.returnCurrentUser();
this.route.data
.subscribe(({aircrafts}) => {
this.aircrafts = aircrafts;
const id = +this.route.snapshot.paramMap.get('id');
console.log(' where are my aircraft!' + JSON.stringify(aircrafts));
this.ELEMENT_DATA = aircrafts;
this.displayedColumns = ['Last Work', 'Rego', 'Make', 'Model', 'Contact', 'Phone'];
this.dataSource = new MatTableDataSource(this.ELEMENT_DATA);
this.dataSource.sort = this.sort;
console.log(id);
if (id) {
this.organisationService.getById(id).subscribe(org => {
this.organisation = org;
});
} else {
console.log('its bad');
}
});
console.log(this.dataSource);
}
The console log under the subscribe is undefined and the console.logs under the service never get triggered. So once again, I find myself not understanding why subscription fire or not fire, or whatever it is that they do.
How do I get past this? thanks everyone.
EDIT: appears that the problem is actually in the ResolverService, I have been able to determine that the data service is getting the results and that they are correct. For some reason, the resolver service can't see them.
The answer was in the route resolver, or rather the app-routing-module. I should have included it in the question, because some of the angular saltys would have picked it up
I was trying to do this:.
{ path: 'organisations/:orgId/aircrafts/:id', component: AircraftsComponent, resolve: {aircrafts : AircraftsResolverService}, canActivate: [AuthGuard] },
But you can't, you have to do this:
{ path: 'organisations/aircrafts/:orgId/:id', component: AircraftsComponent, resolve: {aircrafts : AircraftsResolverService}, canActivate: [AuthGuard] },
results in very non-resty urls, but, hey, whatever works, right?
Here's my AppState class:
export default class AppState {
constructor() {
this.navigationState = {
index: 0,
routes: [{ key: "InitialView" }]
};
}
updateNavigationState(type, route = null) {
switch (type) {
case NavigationStateUpdateType.Push:
console.log(this.navigationState.routes.length);
console.log(this.navigationState.routes[0].key);
if (route !== null) {
console.log("Route: " + route);
this.navigationState = NavigationStateUtils.push(this.navigationState, { key: route });
}
case NavigationStateUpdateType.Pop:
this.navigationState = NavigationStateUtils.pop(this.navigationState);
}
}
}
Now if I do this inside InitialView:
this.props.appState.updateNavigationState(NavigationStateUpdateType.Push, "InitialView1");
Nothing happens. It seems like NavigationStateUtil.pushdoesn't work. Here's what the Console looks like:
AppState.js:18 1
AppState.js:19 InitialView
AppState.js:21 Route: InitialView1
AppState.js:18 1
AppState.js:19 InitialView
AppState.js:21 Route: InitialView1
AppState.js:18 1
AppState.js:19 InitialView
AppState.js:21 Route: InitialView1
Why isn't routes being updated? Or am I doing something wrong?
EDIT
My GlobalNavigation component (as asked):
const { CardStack: NavigationCardStack } = NavigationExperimental;
let GlobalNavigation = class GlobalNavigation extends Component {
render() {
return (React.createElement(NavigationCardStack, {renderScene: this._renderScene.bind(this), navigationState: this.props.appState.navigationState}));
}
_renderScene() {
return (React.createElement(InitialView, {appState: this.props.appState}));
}
};
By the way: all this code is generated by TypeSript.
Simple question, pretty sure it's a complicated answer :)
Is it possible to implement some form of inheritance for viewmodels in Durandal?
So if you have a viewmodel something like this:
define(['durandal/app', 'services/datacontext', 'durandal/plugins/router', 'services/logger'],
function (app, datacontext, router, logger) {
var someVariable = ko.observable();
var isSaving = ko.observable(false);
var vm = {
activate: activate,
someVariable : someVariable,
refresh: refresh,
cancel: function () { router.navigateBack(); },
hasChanges: ko.computed(function () { return datacontext.hasChanges(); }),
canSave: ko.computed(function () { return datacontext.hasChanges() && !isSaving(); }),
goBack: function () { router.navigateBack(); },
save: function() {
isSaving(true);
return datacontext.saveChanges().fin(function () { isSaving(false); })
},
canDeactivate: function() {
if (datacontext.hasChanges()) {
var msg = 'Do you want to leave and cancel?';
return app.showMessage(msg, 'Navigate Away', ['Yes', 'No'])
.then(function(selectedOption) {
if (selectedOption === 'Yes') {
datacontext.cancelChanges();
}
return selectedOption;
});
}
return true;
}
};
return vm;
//#region Internal Methods
function activate(routeData) {
logger.log('View Activated for id {' + routeData.id + '}, null, 'View', true);
});
}
//#endregion
function refresh(id) {
return datacontext.getById(client, id);
}
});
Is it possible to make that into some kind of base type and inherit further viewmodels from it, being able to extend the requires list and so on?
There is another question on this, but the viewmodels don't appear to be quite the same as the one's that I build for durandal/HotTowel.
Thanks.
I'm pretty sure this can be accomplished with jQuery's extend method. This just occurred to me, so there may be something that I'm missing, but a basic example would be something along the lines of:
basevm.js
... your mentioned viewmodel
inheritingvm.js
define(['basevm'], function (basevm) {
var someNewObservable = ko.observable();
var vm = $.extend({
someNewObservable : someNewObservable
}, basevm);
return vm;
});
Please let me know if this works. I just coded from the top of my head and it hasn't been tested.
Just based off what your saying I came up with this. Let me know if this works for you and if it doesn't then let me know what I did wrong.
Thanks.
viewmodelBase
define(['durandal/app', 'services/datacontext', 'durandal/plugins/router', 'services/logger'],
function (app, datacontext, router, logger) {
var vm = function () {
var self = this;
this.someVariable = ko.observable();
this.isSaving = ko.observable(false);
this.hasChanges = ko.computed(function () { return datacontext.hasChanges(); });
this.canSave = ko.computed(function () { return datacontext.hasChanges() && !self.isSaving(); });
};
vm.prototype = {
activate: function (routeData) {
logger.log('View Activated for id {' + this.routeData.id + '}', null, 'View', true);
},
refresh: function (id) {
return datacontext.getById(client, id);
},
cancel: function () {
router.navigateBack();
},
goBack: function () { router.navigateBack(); },
save: function() {
var self = this;
this.isSaving(true);
return datacontext.saveChanges().fin(function () { self.isSaving(false); })
},
canDeactivate: function() {
if (datacontext.hasChanges()) {
var msg = 'Do you want to leave and cancel?';
return app.showMessage(msg, 'Navigate Away', ['Yes', 'No'])
.then(function(selectedOption) {
if (selectedOption === 'Yes') {
datacontext.cancelChanges();
}
return selectedOption;
});
}
return true;
}
};
return vm;
});
parent viewmodel
define([viewmodelBase], function (vmbase) {
var vm1 = new vmbase();
vm1.newProperty = "blah";
var vm2 = new vmbase();
});
I wrote a post on my blog that addresses this issue. In short, I use prototypical inheritance for all of my modal dialog views in one of my projects. Here's the link to the post I wrote (feel free to skip to the code part) and a jsFiddle example that demonstrates it.
Simplified example that can work in Durandal (NOTE: each view-model returns its constructor function, not an object):
viewmodels/modal.js
define(['durandal/system'],
function(system) {
var modal = function () {
this.name = 'Modal';
}
modal.prototype = {
activate: function() {
system.log(this.name + ' activating');
},
attached: function(view) {
system.log(this.name + ' attached');
},
deactivate: function() {
system.log(this.name + ' deactivating');
},
detached: function(view, parent) {
system.log(this.name + ' detached');
}
};
return modal;
});
viewmodels/child.js
define(['durandal/system', 'viewmodels/modal'],
function(system, Modal) {
var child = function() {
this.name = 'Child Modal';
}
// inherits from Modal
child.prototype = new Modal();
child.prototype.constructor = child;
child.prototype._super = Modal.prototype;
// overrides Modal's activate() method
child.prototype.activate = function() {
this._super.activate.call(this); // we can still call it from the _super property
system.log(this.name + ' activating [overridden version]');
};
return child;
});
I prefer this implementation because it supports code reuse, conforms to OOP principles as best as javascript allows, and it gives me the ability to call the base class' methods via the _super property when I need to. You can easily convert this as needed.