ractivejs component nesting - custom-component

The documentation seems to indicate that it is possible to nest custom components within other custom components (http://docs.ractivejs.org/latest/components) :
<Foo on-Bar.foo="barfooed" on-Baz.*="baz-event" on-*.bippy="any-bippy">
<Bar /><Baz /><Baz />
</Foo>
However, the following code only displays the tooltip. The inner custom components al-tt-translation, and al-tt-input are not initialized. In fact, replacing those two components by a string do not lead to that string being passed in anyway to the tooltip custom component .
tooltip = new Ractive({
el: 'airlang-rdt-tt-container',
template: "" +
"<al-tooltip>"+
" <al-tt-translation display='{{display_translation}}' translation_lemma='{{translation_lemma}}' result_rows='{{result_rows}}'/> " +
" <al-tt-input/> "+
"</al-tooltip>",
append: true,
components : {
'al-tooltip': Component_Tooltip,
'al-tt-translation' : Component_TT_Translation,
'al-tt-input' : Component_TT_Input
},
data : {
display_translation : 'block',
translation_lemma : 'example'
}
});
Should I conclude that it is not possible to use the custom tags in the same way than regular HTML tags?
Note : From the documentation, I understand that there is something to do with {{>content}} or {{>yield}} but I can't seem to make it work. What is the right way to do this?

For your example, your <al-tooltip> template needs to have either a {{yield}} or {{>content}} somewhere in it to direct where the contained content should go.
Simple example of how it works:
var Outer = Ractive.extend({ template: '<div>{{yield}}</div>' });
var Inner = Ractive.extend({ template: '<span>hello world</span>' });
var ractive = new Ractive({
el: document.body,
template: '<outer><inner/><inner/></outer>'
components: {
outer: Outer,
inner: Inner
}
})
produces:
<div><span>hello world</span><span>hello world</span></div>
{{yield}} means that the content still runs in the context in which it originated, {{>content}} means import the content as a partial and run it. In your example it probably won't matter because you're using components and not raw templates.

Related

How to add own tags using the tagify plugin, 'Mix text & tags' in particular

I'm creating an input field mixed with tags and inputs using the tagify plugin- section-mix- (see: https://yaireo.github.io/tagify/#section-mix).
The problem is:
Note that tags can only be created if the value matches any of the whitelisted item's value.
I want to add them myself by hitting enter just as in the other examples in the link. Any idea how to solve this with tagify?
HTML
<textarea name='mix'>#cartman and #kyle do not know #homer who is #lisa</textarea>
JAVASCRIPT
var input = document.querySelector('[name=mix]'),
// init Tagify script on the above inputs
tagify = new Tagify(input, {
mode : 'mix', // <-- Enable mixed-content
pattern : /#/, // <-- Tag words which start with # (can be a String instead of Regex)
whitelist : [
{
value: 'Homer'
}
],
dropdown : {
enabled : 1
}
})
var whitelist_2 = ['Homer', 'Marge', 'Bart', 'Lisa', 'Maggie', 'Mr. Burns', 'Ned', 'Milhouse', 'Moe'];
// A good place to pull server suggestion list accoring to the prefix/value
tagify.on('input', function(e){
var prefix = e.detail.prefix;
if( prefix == '#' )
tagify.settings.whitelist = whitelist_2;
if( e.detail.value.length > 1 )
tagify.dropdown.show.call(tagify, e.detail.value);
}
Tagify now supports creating new tags in mix-mode as of version 3.4.0
See tagify's example page
UPDATE 24 FEB, 2020
Currently only tags without spaces can be added due to unresolved complexity in the implementation of how adding new tags in mix-mode works. I will address this soon.

How to overlay a cq widget so it is also available in SiteAdmin

The CQ.tagging.TagInputField provided two configuration parameter which won't work in combination:
tagsBasePath
namespaces
Using the OOTB facebook tags as example, I want to restric the dialog to only display the Favorite Teams. The Structure is this:
So I set tagBasePath to /etc/tags/facebook and namespaces to [favorite_teams]. This does what it is supposed to do and only shows the two teams in the dialog. But when you click on it, a JavaScript exceptions is thrown. The problem lies in the following method defined in /libs/cq/tagging/widgets/source/CQ.tagging.js
CQ.tagging.parseTag = function(tag, isPath) {
var tagInfo = {
namespace: null,
local: tag,
getTagID: function() {
return this.namespace + ":" + this.local;
}
};
// parse tag pattern: namespace:local
var colonPos = tag.indexOf(isPath ? '/' : ':');
if (colonPos > 0) {
// the first colon ":" delimits a namespace
// don't forget to trim the strings (in case of title paths)
tagInfo.namespace = tag.substring(0, colonPos).trim();
tagInfo.local = tag.substring(colonPos + 1).trim();
}
return tagInfo;
};
It does not respect the configurations set on the widget and returns a tagInfo where the namespace is null. I then overlayed the method in my authoring JavaScripts, but this is of course not working in the SiteAdmin as my custom JS are not included.
So, do I really have to overwrite the CQ.tagging.js below libs or can I somehow inject my overlay into the SiteAdmin so the PageProperties Dialog opened from there works as well?
UPDATE: I had a chat with Adobe support regarding this and it was pointed out that if you use tagsBasePath you need to place it somewhere else than below /etc/tags. But this won't work as well as the TagListServlet will return no tags as /etc/tags is also fixed in the TagManager as the tagsBasePath. I now overwrite the above mentioned js at its location, being well aware that I need to check it if we install a hotfix or an update. Is someone has a more elegant solution I'd be still thankful.

approach for validated form controls in AngularJS

My teammates and I are learning AngularJS, and are currently trying to do some simple form field validation. We realize there are many ways to do this, and we have tried
putting input through validation filters
using a combination of controller and validating service/factory
a validation directive on the input element
a directive comprising the label, input and error output elements
To me, the directive approach seems the most "correct". With #3, we ran into the issue of having to communicate the validation result to the error element (a span sibling). It's simple enough to do some scope juggling, but it seemed "more correct" to put the span in the directive, too, and bundle the whole form control. We ran into a couple of issue, and I would like the StackOverflow community's input on our solution and/or to clarify any misunderstandings.
var PATTERN_NAME = /^[- A-Za-z]{1,30}$/;
module.directive("inputName", [
function () {
return {
restrict: "E",
require: "ngModel",
scope: {
fieldName: "#",
modelName: "=",
labelName: "#",
focus: "#"
},
template: '<div>' +
'<label for="{{fieldName}}">{{labelName}}</label>' +
'<input type="text" ng-model="modelName" id="{{fieldName}}" name="{{fieldName}}" placeholder="{{labelName}}" x-blur="validateName()" ng-change="validateName()" required>' +
'<span class="inputError" ng-show="errorCode">{{ errorCode | errorMsgFltr }}</span>' +
'</div>',
link: function (scope, elem, attrs, ngModel)
{
var errorCode = "";
if (scope.focus == 'yes') {
// set focus
}
scope.validateName = function () {
if (scope.modelName == undefined || scope.modelName == "" || scope.modelName == null) {
scope.errorCode = 10000;
ngModel.$setValidity("name", false);
} else if (! PATTERN_NAME.test(scope.modelName)) {
scope.errorCode = 10001;
ngModel.$setValidity("name", false);
} else {
scope.errorCode = "";
ngModel.$setValidity("name", true);
}
};
}
};
}
]);
used as
<form novalidate name="addUser">
<x-input-name
label-name="First Name"
field-name="firstName"
ng-model="firstName"
focus="yes"
model-name="user.firstName">
</x-input-name>
<x-input-name
label-name="Last Name"
field-name="lastName"
ng-model="lastName"
model-name="user.lastName">
</x-input-name>
...
</form>
First, because both form and input are overridden by AngularJS directives, we needed access to the ngModel API (ngModelController) to allow the now-nested input to be able to communicate validity to the parent FormController. Thus, we had to require: "ngModel", which becomes the ngModel option to the link function.
Secondly, even though fieldName and ngModel are given the same value, we had to use them separately. The one-way-bound (1WB) fieldName is used as an attribute value. We found that we couldn't use the curly braces in an ngModel directive. Further, we couldn't use a 1WB input with ngModel and we couldn't use a two-way-bound (2WB) input with values that should be static. If we use a single, 2WB input, the model works, but attributes like id and name become the values given to the form control.
Finally, because we are sometimes reusing the directive in the same form (e.g., first name and last name), we had to make attributes like focus parameters to be passed in.
Personally, I would also like to see the onblur and onchange events bound using JavaScript in the link function, but I'm not sure how to access the template markup from within link, especially outside/ignorant of the larger DOM.

Is it possible to add angularized html tags and attributes in a directive based on model data in Angularjs?

I want to build a directive that will build form inputs based on a nested object of settings that include the input type, model to bind, and html attributes. I have been pounding my head and, perhaps, am close to concluding that this is something Angular is not equipped to do. I would like to build a directive that could take an array of objects like:
[{
"label":"When did it happen",
"model": $scope.event.date,
"element":"input",
"type": "date",
"options":{
"class":"big",
"required": true,
},
},{
"label":"How did it happen?",
"model": $scope.event.cause,
"element":"textarea",
"options":{
"cols":45,
"rows":55,
},
},{
"label":"How many times did it happen?",
"model": $scope.event.times,
"element":"input",
"options":{},
}],
I have struggled through many different aspects of directives. I keep running across a few issues.
Neither the template nor the controller directive functions have access to any sort of scope that could reach any sort of data--most especially my array I've made. This means that I can't decide how to build my DOM (i.e. tag types and attributes) until later.
All compiling is done before the linking function. I am able to manipulate the DOM in the linking function but none of it is angularized. This means if I add a required attribute angular's ngValidate is not aware of it. If I try and change the tag type, I reset lose my model binding etc.
It seems that this is just the way angular runs. Is there no good way to affect the DOM tag types and attributes based on model data without specifying everything out specifically?
How about something like this:
$scope.event = {
date: new Date(),
cause:'It was the colonel in the kitchen with the revolver',
time:5, //...
$metadata: data
};
Where data here is simply the array you have already shown. (apart from the model property which would just be a string representing the property of the event, like so:
{
"label":"When did it happen",
"model":'date',
"element":"input",
"type": "date",
"options":{
"class":"big",
"required": true,
}
}
Then your directive would simply access the property given it on the parent scope. (event) And process the metadata into a usable template. Which could then get compiled.
Here is such a directive, and the directive...
myApp.directive('contentForm',function($compile,$interpolate){
var template = "<span class='lbl'>{{label}}</span><{{element ||'input'}} {{options}}"+
" ng-model='{{root+'.'+model}}'></{{element}}>";
function linkerFunction(scope,element,attrs){
if(!scope[attrs.contentForm])return;
var metadata = scope[attrs.contentForm].$metadata || false;
if(!metadata)return;
element.html(getHtml(metadata,attrs.contentForm));
$compile(element.contents())(scope);
}
return {
restrict: 'A',
replace: true,
link:linkerFunction
};
//interpolate the template with each set of metadata
function getHtml(metadata,root){
var interpolation = $interpolate(template);
var html = '';
if(angular.isArray(metadata)){
for(var i = 0; i < metadata.length; i++){
metadata[i].root = root;
metadata[i].options = processOptions(metadata[i].options);
html += interpolation(metadata[i]) + '</br>'
}
}else{
html = interpolation(metadata);
metadata.options = processOptions(metadata.options);
}
return html;
}
// parse object into html attributes
function processOptions(options){
var result = '';
for(var key in options){
if(options.hasOwnProperty(key)){
result += ' '+key+"='"+options[key]+"'"
}
}
return result.trim();
}
});
You'd probably want to change the template. I seem to have forgotten to put in a type. But that should be fairly straight forward. I hope this helps!

EmberJS pre 2 used to pass in the JS event object for actions

# in the handlebars template
{{action "do_something" target="view"}}
# in the view
APP.view = Ember.View.extend(
do_something: (evt) ->
console.log evt #this used to contain a javascript event object, it was useful at times :(
)
I know I can pass in a context. But I want to know if there is a way to get the actual event.
if you want an event object you'll need to create a custom View. Here is an example of something I used in my App:
App.ProductsGridSortButtonView = Ember.View.extend({
tagName: 'a',
classNames: ['productsSortButton'],
attributeBindings: ['data-sort','data-sort-type'],
click: function(e){
this.get('parentView').sortProducts(e);
}
});
and in the template:
{{#view App.ProductsGridSortButtonView data-sort="price" data-sort-type="number"}}