I'm using Aurelia-Dragula (https://github.com/michaelmalonenz/aurelia-dragula) in my application and I want to set my options per container like in non-Aurelia Dragula.
In my case I want to have ContainerA where I want option.copy = true and ContainerB where I want option.removeOnSpill = true. so I tried both:
.plugin('aurelia-dragula', (options) => {
options.removeOnSpill = true;
options.copy = true;
})
But the result is that copy reigns and removeOnSpill doesn't work.
How options variable looks in aurelia-dragula when logged to the console:
{"containers":[],"copy":true,"copySortSource":false,"revertOnSpill":true,"removeOnSpill":true,"direction":"vertical","ignoreInputTextSelection":true,"mirrorContainer":{}}
Example of how it's done in non-Aurelia Dragula (source: https://bevacqua.github.io/dragula/):
dragula([document.getElementById(left), document.getElementById(right)], {
copy: function (el, source) {
return source === document.getElementById(left)
},
accepts: function (el, target) {
return target !== document.getElementById(left)
}
});
Due to how different the options is set and I can't find documentation for this in aurelia-dragula I'm not able to translate it.
Righto - so yes, it's absolutely possible.
I haven't tested this, but I believe this will work:
view.html
<template>
<aurelia-dragula containers.one-way="containers" copy.call="shouldCopy(item, container)" accepts.call="shouldAccept(item, target, source, reference)"></aurelia-dragula>
</template>
viewmodel.js
export class ViewModel {
get containers () {
return [document.getElementById(left), document.getElementById(right)]
}
shouldCopy (item, container) {
return container === document.getElementById(left)
}
shouldAccept(item, target, source, reference) {
return target !== document.getElementById(left)
}
}
Related
I have the following bit of code:
Which prints the following in the console:
I've been bashing my head for a very long time, not sure where to go from here. It was working just fine when I pushed last. Then, I made some changes which broke it as you can see. To try to fix it, I stashed my changes, but I'm still getting this error.
Edit
search: throttle(live => {
let vm = this;
console.log("entered!!!");
console.log("this", this);
console.log("vm", vm);
if (typeof live == "undefined") {
live = true;
}
if (!live) {
// We are on the search page, we need to update the results
if (vm.$route.name != "search") {
vm.$router.push({ name: "search" });
}
}
vm.$store.dispatch("search/get", {
type: vm.searchType,
query: vm.searchQuery
});
}, 500)
Assuming search is in your methods it should not be using an arrow function as that will give you the wrong this binding.
Instead use:
methods: {
search: throttle(function (live) {
// ...
}, 500)
}
Here I'm also assuming that throttle will preserve the this value, which would be typical for implementations of throttling.
Like I said in my comment, I suspect this is a scoping issue.
Perhaps if you return the throttle function with the Vue component passed in, you might see better results:
search: function() {
let vm = this;
return throttle(live => {
console.log("entered!!!");
console.log("this", this);
console.log("vm", vm);
if (typeof live == "undefined") {
live = true;
}
if (!live) {
// We are on the search page, we need to update the results
if (vm.$route.name != "search") {
vm.$router.push({ name: "search" });
}
}
vm.$store.dispatch("search/get", {
type: vm.searchType,
query: vm.searchQuery
});
}, 500)
}
I have a custom attribute that processes authentication data and does some fun stuff based on the instructions.
<div auth="disabled: abc; show: xyz; highlight: 123">
There's a lot of complicated, delicate stuff happening in here and it makes sense to keep it separate from semantic bindings like disabled.bind. However, some elements will have application-logic level bindings as well.
<div auth="disabled.bind: canEdit" disabled.bind="!editing">
Under the covers, my auth attribute looks at the logged in user, determines if the user has the correct permissions, and takes the correct action based on the result.
disabledChanged(value) {
const isDisabled = this.checkPermissions(value);
if (isDisabled) {
this.element.disabled = true;
}
}
This result needs to override other bindings, which may or may not exist. Ideally, I'd like to look for an existing Binding and override it ala binding behaviors.
constructor(element) {
const bindings = this.getBindings(element); // What is the getBindings() function?
const method = bindings['disabled']
if (method) {
bindings['disabled'] = () => this.checkPermission(this.value) && method();
}
}
The question is what is this getBindings(element) function? How can I access arbitrary bindings on an element?
Edit: Gist here: https://gist.run/?id=4f2879410506c7da3b9354af3bcf2fa1
The disabled attribute is just an element attribute, so you can simply use the built in APIs to do this. Check out a runnable example here: https://gist.run/?id=b7fef34ea5871dcf1a23bae4afaa9dde
Using setAttribute and removeAttribute (since the disabled attribute does not really have a value, its mere existence causes the element to be disabled), is all that needs to happen:
import {inject} from 'aurelia-framework';
#inject(Element)
export class AuthCustomAttribute {
constructor(element) {
this.el = element;
}
attached() {
let val = false;
setInterval(() => {
if(this.val) {
this.el.setAttribute('disabled', 'disabled');
} else {
this.el.removeAttribute('disabled');
}
this.val = !this.val;
}, 1000);
}
}
NEW RESPONSE BELOW
You need to work directly with the binding engine. A runnable gist is located here: https://gist.run/?id=b7fef34ea5871dcf1a23bae4afaa9dde
Basically, you need to get the original binding expression, cache it, and then replace it (if auth === false) with a binding expression of true. Then you need to unbind and rebind the binding expression:
import {inject} from 'aurelia-framework';
import {Parser} from 'aurelia-binding';
#inject(Element, Parser)
export class AuthCustomAttribute {
constructor(element, parser) {
this.el = element;
this.parser = parser;
}
created(owningView) {
this.disabledBinding = owningView.bindings.find( b => b.target === this.el && b.targetProperty === 'disabled');
if( this.disabledBinding ) {
this.disabledBinding.originalSourceExpression = this.disabledBinding.sourceExpression;
// this expression will always evaluate to true
this.expression = this.parser.parse('true');
}
}
bind() {
// for some reason if I don't do this, then valueChanged is getting called before created
this.valueChanged();
}
unbind() {
if(this.disabledBinding) {
this.disabledBinding.sourceExpression = this.disabledBinding.originalSourceExpression;
this.disabledBinding.originalSourceExpression = null;
this.rebind();
this.disabledBinding = null;
}
}
valueChanged() {
if(this.disabledBinding ) {
if( this.value === true ) {
this.disabledBinding.sourceExpression = this.disabledBinding.originalSourceExpression;
} else {
this.disabledBinding.sourceExpression = this.expression;
}
this.rebind();
} else {
if( this.value === true ) {
this.el.removeAttribute('disabled');
} else {
this.el.setAttribute('disabled', 'disabled');
}
}
}
rebind() {
const source = this.disabledBinding.source;
this.disabledBinding.unbind();
this.disabledBinding.bind(source);
}
}
It is important that the attribute clean up after itself, as I do in the unbind callback. I'll be honest that I'm not sure that the call to rebind is actually necessary in the unbind, but it's there for completeness.
I'm aware of click.trigger as well as click.delegate which work fine. But what if I want to assign a click event that should only trigger when the exact element that has the attribute gets clicked?
I'd probably do something like this were it "normal" JS:
el.addEventListener('click', function (e) {
if (e.target === el) {
// continue...
}
else {
// el wasn't clicked directly
}
});
Is there already such an attribute, or do I need to create one myself? And if so, I'd like it to be similar to the others, something like click.target="someMethod()". How can I accomplish this?
Edit: I've tried this which doesn't work because the callback function's this points to the custom attribute class - not the element using the attribute's class;
import { inject } from 'aurelia-framework';
#inject(Element)
export class ClickTargetCustomAttribute {
constructor (element) {
this.element = element;
this.handleClick = e => {
console.log('Handling click...');
if (e.target === this.element && typeof this.value === 'function') {
console.log('Target and el are same and value is function :D');
this.value(e);
}
else {
console.log('Target and el are NOT same :/');
}
};
}
attached () {
this.element.addEventListener('click', this.handleClick);
}
detached () {
this.element.removeEventListener('click', this.handleClick);
}
}
And I'm using it like this:
<div click-target.bind="toggleOpen">
....other stuff...
</div>
(Inside this template's viewModel the toggleOpen() method's this is ClickTargetCustomAttribute when invoked from the custom attribute...)
I'd also prefer if click-target.bind="functionName" could instead be click.target="functionName()" just like the native ones are.
Just use smth like click.delegate="toggleOpen($event)".
$event is triggered event, so you can handle it in toggleOpen
toggleOpen(event) {
// check event.target here
}
Also you can pass any other value available in template context to toggleOpen.
Is there any kind of wildcard operator in the Durandal observable plugin, as there is in (for example) JsObservable?
The Durandal observable documentation gives this example:
var observable = require('plugins/observable');
var viewModel:{
firstName:'',
lastName:''
};
observable(viewModel, 'firstName').subscribe(function(value){
console.log('First name changed.');
});
viewModel.firstName = 'Test';
What I'd like to do is use a wildcard to subscribe to any changed property on the target. Something like this:
observable(viewModel, '*').subscribe(function(property, value){
console.log(property + ' changed.');
});
I don't see anything in the API documentation, but wondered if there was anything undocumented, or if anyone has a workaround to implement this behaviour.
Unfortunately, there is no wildcard operator for this functionality.
But you can easily create wrapper module for this functionality.
Here is small example:
var observable = require('plugins/observable');
var wildcardObservable = function(obj, changeCallback){
for(var prop in obj){
observable(obj, prop).subscribe(changeCallback);
}
}
var changeCallback = function() {
console.log('property changed.');
}
Usage:
var viewModel:{
firstName:'',
lastName:''
};
wildcardObservable(viewModel, changeCallback);
With thanks to U10 for the start above, (and with reference to a few examples on the web) I came up with the following, which uses a closure to track all the necessary properties. It's a bit messy but it does what I need for now - hopefully it will be of use to someone.
var ChangeTracker = (function () {
function ChangeTracker() {
}
ChangeTracker.prototype._trackChange = function (prop, target) {
var type = typeof (target[prop]);
var value = target[prop];
_logger.log("_trackChange", { target: target, prop: prop, type: type, value: value }, "CT");
_obs(target, prop).subscribe(function (newValue) {
var obj = {
target: target,
prop: prop,
newValue: newValue,
oldValue: value
};
_logger.log(">>>>>>>>>>>>>>> CHANGE!", obj, "CT");
value = newValue;
});
};
ChangeTracker.prototype.TrackChanges = function (target) {
var _this = this;
for (var prop in target) {
if (target.hasOwnProperty(prop)) {
this._trackChange(prop, target);
}
var underlying = ko.utils.unwrapObservable(target[prop]);
if (underlying instanceof Array) {
ko.utils.arrayForEach(underlying, function (item) {
_this.TrackChanges(item);
});
} else if (typeof underlying === "object") {
this.TrackChanges(underlying);
}
}
}
};
return ChangeTracker;
})();
I am trying to figure out how to use a custom view location strategy, I have read the documentation at this page http://durandaljs.com/documentation/Using-Composition/ but I don't exactly understand what the strategy function should look like.
Can anybody give me a quick example of what the implementation of this function would be like and the promise that returns (even a simple one) etc?
Thanks in advance,
Gary
p.s. This is the code in my html:
<div>
<div data-bind="compose: {model: 'viewmodels/childRouter/first/simpleModel', strategy:
'viewmodels/childRouter/first/myCustomViewStrategy'}"></div> </div>
and this is the code in my myCustomViewStrategy:
define(function () {
var myCustomViewStrategy = function () {
var deferred = $.Deferred();
deferred.done(function () { console.log('done'); return 'simpleModelView'; });
deferred.fail(function () { console.log('error'); });
setTimeout(function () { deferred.resolve('done'); }, 5000);
return deferred.promise();
};
return myCustomViewStrategy;
});
but I get the error:
Uncaught TypeError: Cannot read property 'display' of undefined - this is after done has been logged in the console window.
Okay I solved this by creating my custom view strategy by the following:
define(['durandal/system', 'durandal/viewEngine'], function (system, viewEngine) {
var myCustomViewStrategy = function () {
return viewEngine.createView('views/childRouter/first/sModelView');
}
return myCustomViewStrategy;
});
As I found the documentation a bit lacking on compose binding's strategy setting I checked the source code how it works. To summ it up:
The module specified by the compose binding's strategy setting by its moduleId
must return a function named 'strategy'
which returns a promise which results in the view to be bound
as a HTML element object.
As a parameter the strategy method receives the compose binding's settings object
with the model object already resolved.
A working example:
define(['durandal/system', 'durandal/viewEngine'], function (system, viewEngine) {
var strategy = function(settings){
var viewid = null;
if(settings.model){
// replaces model's module id's last segment ('/viewmodel') with '/view'
viewid = settings.model.__moduleId__.replace(/\/[^\/]*$/, '/view');
}
return viewEngine.createView(viewid);
};
return strategy;
});
Durandal's source:
// composition.js:485
for (var attrName in settings) {
if (ko.utils.arrayIndexOf(bindableSettings, attrName) != -1) {
/*
* strategy is unwrapped
*/
settings[attrName] = ko.utils.unwrapObservable(settings[attrName]);
} else {
settings[attrName] = settings[attrName];
}
}
// composition.js:523
if (system.isString(context.strategy)) {
/*
* strategy is loaded
*/
system.acquire(context.strategy).then(function (strategy) {
context.strategy = strategy;
composition.executeStrategy(context);
}).fail(function(err){
system.error('Failed to load view strategy (' + context.strategy + '). Details: ' + err.message);
});
} else {
this.executeStrategy(context);
}
// composition.js:501
executeStrategy: function (context) {
/*
* strategy is executed
* expected to be a promise
* which returns the view to be bound and inserted to the DOM
*/
context.strategy(context).then(function (child) {
composition.bindAndShow(child, context);
});
}