How to let ng-disabled directive work with isolated scope - scope

Recently I have to make a Input element work with both ng-disabled and an custom directive which use isolated scope to evaluate expression just like what ng-disabled is doing, somehow, the custom directive works fine but ng-disabled doesn't, since it only evaluate expression within the isolated scope.
The custom directive is quite simple like:
angular
.module('directives', [])
.directive('conditionalAutofocus', function () {
return {
restrict:'A',
scope:{
condition:'&conditionalAutofocus'
},
link:function (scope, element, attrs) {
if (scope.condition()) {
attrs.$set('autofocus','true');
}
}
}
});
while the page looks like:
<input name="pin"
ng-model="pin"
type="password"
required
ng-disabled="names == null"
conditional-autofocus="names != null" />
Anybody already has solution for this issue?
Thanks in advance!
Yanni

I had this same issue, and the easiest solution imho. is to use the isolated scope to inherit the property of ngDisabled.
angular.module('directives', [])
.directive('conditionalAutofocus', function () {
return {
restrict:'A',
scope:{
condition:'&conditionalAutofocus',
disabled:'=ngDisabled'
},
link:function (scope, element, attrs) {
if (scope.condition()) {
attrs.$set('autofocus','true');
}
if(scope.disabled){
//is disabled
}
}
}
});
Might only work for restrict : 'E'. Have not tested others

I had a similar problem lately. I wanted to disable a button in isolated scope and use this nice angular ng-disabled directive.
After some digging I came to a solution like this:
link: function($scope, element, attrs){
$scope.$parent.$watch(attrs.ngDisabled, function(newVal){
element.prop('disabled', newVal);
});
//...
}
To evaluate the ng-diabled expression instead of $scope just jump to $scope.$parent and all your variables will be avaliable. Unfortunatly manually setting the disabled property is required.

OK, for my own case above my solution is to change the implementation of directive, not use isolated scope anymore:
angular.module('directives', [])
.directive('conditionalAutofocus', function () {
return {
restrict:'A',
link:function (scope, element, attrs) {
scope.$watch(attrs.conditionalAutofocus, function(){
if (scope.$eval(attrs.conditionalAutofocus)) {
element.focus();
}else{
element.blur();
}
});
}
}
});

You can set up a bi-directional binding to the parent scope in your isolated scope definition. Then add a watch on the isolated scope property. This just duplicates the code in the ngReadonly directive.
angular.module('directives', [])
.directive('conditionalAutofocus', function () {
return {
restrict:'A',
scope:{
condition: '&conditionalAutofocus',
isReadonly: '=ngReadonly'
},
link:function (scope, element, attrs) {
if (scope.condition()) {
attrs.$set('autofocus','true');
}
scope.$watch('isReadonly', (value) => {
attrs.$set('readonly', !!value);
});
}
}
});

You don't need any of the complexity of the other answers. The problem is that your Directive doesn't even know wtf ng-Disabled is. If you want to use ng-Disabled in your template, you have to inject it into your scope or requirements. For example, this Will NOT work:
.directive('myDirective', function () {
return {
restrict: 'E',
template: '<input type="text" ng-disabled="fieldDisabled" />',
scope:{ something: '=' },
link: function (scope, element, attrs) {
scope.something = attrs.something;
},
controller: function($scope) {
$scope.fieldDisabled = false;
$scope.myAction = function() {
$scope.fieldDisabled = true;
};
}
}
});
But the following Will work:
.directive('myDirective', function () {
return {
restrict: 'E',
template: '<input type="text" ng-disabled="fieldDisabled" />',
scope:{ something: '=', lol: '=ngDisabled' },
link: function (scope, element, attrs) {
scope.something = attrs.something;
},
controller: function($scope) {
$scope.fieldDisabled = false;
$scope.myAction = function() {
$scope.fieldDisabled = true;
};
}
}
});
Note that the only change is lol: '=ngDisabled'. That makes it work because now your directive knows what ngDisabled is. You don't have to use lol or do any other special wiring. You can use your controller just like any other controller now with your own variables.

Related

Tiptap how to create a paragraph (p) on Shift-Enter, instead of a br?

Using TipTap, I'm trying to avoid adding a <br />, but create a <p></p> instead, with the focus inside that <p>|</p> when the user hit shift-Enter but I can't make it work.
Here's what I did so far:
new (class extends Extension {
keys () {
return {
'Shift-Enter' (state, dispatch, view) {
const { schema, tr } = view.state
const paragraph = schema.nodes.paragraph
console.log(tr.storedMarks)
const transaction = tr.deleteSelection().replaceSelectionWith(paragraph.create(), true).scrollIntoView()
view.dispatch(transaction)
return true
}
}
}
})()
How can I do this?
I don't know if this is still relevant but as I was looking for the same thing, I found two ways to make this work.
NOTE:
I'm using tiptap v2, if that's not a problem, then:
I overrode the HardBreak extension, since it's the one that use the Shift-Enter keybinding. It looks something like;
const CustomHardBreak = HardBreak.extend({
addKeyboardShortcuts() {
return {
"Mod-Enter": () => this.editor.commands.setHardBreak(),
"Shift-Enter": () => this.editor.commands.addNewline(),
};
},
});
And used it like so;
editor = new Editor({
extensions: [
customNewline,
CustomHardBreak,
]
});
Use the default editor command createParagraphNear. E.g this.editor.commands.createParagraphNear()
I tried creating a custom extension from your code and ended up with something similar to the command above, i.e;
export const customNewline = Extension.create({
name: "newline",
priority: 1000, // Optional
addCommands() {
return {
addNewline:
() =>
({ state, dispatch }) => {
const { schema, tr } = state;
const paragraph = schema.nodes.paragraph;
const transaction = tr
.deleteSelection()
.replaceSelectionWith(paragraph.create(), true)
.scrollIntoView();
if (dispatch) dispatch(transaction);
return true;
},
};
},
addKeyboardShortcuts() {
return {
"Shift-Enter": () => this.editor.commands.addNewline(),
};
},
});
And added this as an extension in my editor instance.
PS:
They both work, almost exactly the same, I haven't found a difference yet. But there's somewhat of a 'catch' if you would call it that; Both these methods don't work on empty lines/nodes, a character has to be added before the cursor for it to work, any character, even a space.
In TipTap 2.0 I am able to use this custom extension:
const ShiftEnterCreateExtension = Extension.create({
addKeyboardShortcuts() {
return {
"Shift-Enter": ({ editor }) => {
editor.commands.enter();
return true;
},
};
},
});
To make shift + enter behave like enter.
In my case I actually wanted enter to do something different. So I use prosemirror events to set a ref flag on whether shift was pressed. Than I check that flag under the "Enter" keyboard event -- which could be triggered normally or through the shift + enter extension.

Best way to navigate throught a JSON in Node while validating the path

I'm trying to get some info out of a API call in Nodejs, structured something like a JSON:
{
"generated":"2019-11-04T09:34:11+00:00",
"event":{
"id":"19040956",
"start_":"2019-11-16T11:30:00+00:00",
"event_context":{
"sport":{
"id":"1",
"name":"Soccer"
}
}
}
}
I'm not sure about the presence of none of these fields(Json could be incomplete).
Is there a better way to get the value of "name" in JSON.event.event_context.sport.name without an ugly if to not get errors like "cannot get field 'sport' of undefined"?
Currently, I'm doing
if(json.event && json.event.event_context && json.event.event_context.sport) {
return json.event.event_context.sport.name;
}
Is there a better way?
Thank you!
what do you mean by saying "I'm not sure about the presence of none of these fields"?
i don't understand what your'e trying to achieve.
Looks like there is also an interesting package that will allow more conditions on searching json :
https://www.npmjs.com/package/jspath
let getNested = (path, obj) => {
return path.split(".").reduce( getPath, obj);
}
let getPath = (path, key) => {
return (path && path[key]) ? path[key] : null
}
let test = {
"foo": "bar",
"baz": { "one": 1, "two": ["to", "too", "two"] },
"event": { "event_context": { "sport": { "name": "soccer" } } }
}
console.log(getNested("none", test))
console.log(getNested("baz.one", test))
console.log(getNested("baz.two", test))
console.log(getNested("event.event_context.sport.name", test))
You can use lodash get to get a potentially deeply-nested value, and also specify a default in case it doesnt exist.
Example
const _ = require('lodash');
const my_object = {
"generated":"2019-11-04T09:34:11+00:00",
"event":{
"id":"19040956",
"start_":"2019-11-16T11:30:00+00:00",
"event_context":{
"sport":{
"id":"1",
"name":"Soccer"
}
}
};
_.get(my_object, 'event.event_context.sport.name'); // "Soccer"
_.get(my_object, 'event.event_context.sport.nonExistentField', 'default val'); // "default val"
Article: https://medium.com/#appi2393/lodash-get-or-result-f409e73e018b
You can check by using a function to check object keys like :
function checkProperty(checkObject, checkstring){
if(!checkstring)
return false;
var propertiesKeys = checkstring.split('.');
propertiesKeys.forEach(element => {
if(!checkObject|| !checkObject.hasOwnProperty(element)){
return false;
} else {
checkObject= checkObject[element];
}
})
return true;
};
var objectToCheck = {
"generated":"2019-11-04T09:34:11+00:00",
"event":{
"id":"19040956",
"start_":"2019-11-16T11:30:00+00:00",
"event_context":{
"sport":{
"id":"1",
"name":"Soccer"
}
}
}
}
if (checkProperty(objectToCheck ,'event.event_context.sport.name'))
console.log('object to find is : ', objectToCheck .event.event_context.sport.name;)
Yeah there are better ways!
For example, you could use lodash's get() method to reach a nested value.
var object = { 'a': [{ 'b': { 'c': 3 } }] };
_.get(object, 'a[0].b.c');
// => 3
But there is also a native solution.
Currently (11.2019) only Babel can handle this.
I am speaking of Optional chaining. It's new in the Ecmascript world.
Why I like it? Look here!
// Still checks for errors and is much more readable.
const nameLength = db?.user?.name?.length;
What happens when db, user, or name is undefined or null? With the optional chaining operator, JavaScript initializes nameLength to undefined instead of throwing an error.
If you are using Babel as a compiler then you could use it now.
Related link: https://v8.dev/features/optional-chaining

Error: wait.for can only be called inside a fiber

I have 2 scipts almost identical with a cascade of function calls nested in a fiber.
This one (parsing Tx in a blockchain) with three calls works perfectly
wait.launchFiber(blockchain)
function blockchain() {
foreach block {
parseBlock (blockIndex)
}
}
function parseBlock(blockIndex) {
foreach Tx in block {
parseTx(txHash)
}
}
function parseTx (txHash) {
if ( txHashInDB(txHash) ) {
do something
}
}
function txHashInDB (txHash) {
var theTx = wait.forMethod(Tx, 'findOne', {'hash': txHash});
return (theTx) ? true : false;
}
Then I have to do something similar with the mempool. In this case I don't have blocks, only transactions, so I have only 2 calls and I get this error message:
Error: wait.for can only be called inside a fiber
wait.launchFiber(watchMempool);
function watchMempool() {
web3.eth.filter('pending', function (error, txHash) {
parseTx(txHash);
});
}
function parseTx (txHash) {
if ( txHashInDB(txHash) ) {
do something
}
}
function txHashInDB (txHash) {
var theTx = wait.forMethod(Tx, 'findOne', {'hash': txHash});
return (theTx) ? true : false;
}
I don't understand what the problem is. Those two scripts have the same structure !
I think for array functions like map or filter you need to use the wait.parallel extensions, i.e. in your case something like:
function watchMempool() {
wait.parallel.filter(web3.eth, parseTx);
}
(Note: I'm just assuming web3.eth is an array; if not, you should probably add a bit more context to your question, or try to boil down the problem to a more generic example).

Is there a way to create a dynamic partial resulting from server treatment?

I've started to use NodeJS for a couple of months now and I came across a little problem with partials rendering.
I'd like to include a partial view in some templates but I want this partial to be dynamically generated from the server (because it depends on data retrieved from DB and other stuff).
I tried to create a template helper to do that but as the processing needs to be done asynchronously I can't get an html return to write within my template.
Basically what would be the best for me would be something similar to (this code does not work obviously):
template_file.js
...
<div>
<%- generatePartial(data) %>
</div>
...
helper_middleware.js
module.exports = function registerAppHelpers(request, response, next)
{
var appHelpers = {};
appHelpers.generatePartial = function generatePartial(data)
{
if (request.isAuthenticated())
{
DB.findOne({ id: request.user.id }, function found(error, obj)
{
if (error)
...
if (obj)
{
return generatePartial1(data);
}
else
{
return generatePartial2(data);
}
});
}
else
{
return generatePartial3(data);
}
};
// Register the helpers as local variables to be accessed within a template.
for (var helper in appHelpers) {
response.locals[helper] = appHelpers[helper];
}
next();
};
Now I may be completely wrong about the way I want to deal with this problem, so if you have any solution/other suggestions about that do not hesitate.
PS : I use ExpressJS and EJS.
I think you are going completely in a wrong direction..
What ejs is for?
ejs is javascript embedded in html so you can create dynamic html.
so whatever logic you have just write it inside the ejs template and let it handle everything. you just need to pass the information to ejs engine.
So instead of
if (obj)
{
return generatePartial1(data);
}
else
{
return generatePartial2(data);
}
I would suggest to capture the whole data
if (obj)
{
array1.push(data);
}
else
{
array2.push(data);
}
and then pass this whole bunch of data to ejs, write the conditions and all logic in ejs file, and let it handle the html logic.
for ex.
res.render('template_file.js', {
array1: array1,
array2: array2
});

YUI, instantiable module that is not a widget?

If I want a module that is instantiable, let say, a module that handles storing preferences in a subcookies, and i want the main cookie to be configurable, but i don't want it to be a widget... what patterns should i use with YUI?
the end code should be something:
Y.use('my-pref-manager', function(Y){
var A = Y.my-pref-manager.prefStore('A"),
B = Y.my-pref-manager.prefStore('B");
// A and B are now loaded with the contents of cookies A and B, if they exist
A.set('xy', 123 );
});
So far i either found patterns that create widgets within my-module or i have to use methods directly in my-method which will be globals and lack initializers, etc.
There is a bunch of ways of doing this. You could do it using Y.Base.create, like below. The code might not be production ready, or even working properly, but hopefully it answers how you can create a module without it being a Widget.
The code below creates a module that extends Y.Base. This let us use Attributes and other cool things. Check the doc for Y.Base.
YUI.add('my-pref-manager', function (Y) {
var PrefManager = Y.Base.create('myPrefManager', Y.Base, [], {
initializer: function () {
this.after('prefsChange', this.changePref);
},
changePref: function (e) {
Y.Cookie.setSub(this.get('prefStore'), e.subAttrName, this.get(e.subAttrName));
},
setPref: function (name, val) {
this.set('prefs.'+name, val);
},
getPref: function (name) {
return this.get('prefs.'+name);
}
}, {
ATTRS: {
prefStore: {
value: null,
setter: function (val) {
return Y.Cookie.set(val, val);
}
},
prefs: {
value: {}
}
}
});
Y.namespace('My').PrefManager = PrefManager;
}, '0.0.1', {
requires: ['base', 'cookie']
});
YUI().use('my-pref-manager', function (Y) {
var A = new Y.My.PrefManager({prefStore: 'userPrefs'}),
B = new Y.My.PrefManager({prefStore: 'displayPrefs'});
A.setPref('x', 3);
A.setPref('y', 54);
B.setPref('tty', 7);
console.log(A.getPref('x')); // 3
});
Try it out: http://jsfiddle.net/B62nu/

Resources