Handlebars EACH iterator error - javascript

I'm new to Handlebars and while I found a workaround, I'm wondering why one registered helper works and one doesn't. The example that doesn't work is an example from the HB docs.
HTML:
<ul class="global-nav clearfix">
{{#each data}}
<li>{{text}}</li>
{{/each}}
</ul>
...
<ul class="content-nav clearfix">
{{#each data}}
<li>{{text}}</li>
{{/each}}
</ul>
Data:
var nav = [
{
name: 'global',
selector: $('.global-nav'),
data: [
{
text: 'Page 1',
href: 'page1.html'
}, {
text: 'Page 2',
href: 'page2.html'
}
],
name: 'content',
selector: $('.content-nav'),
data: [
{
text: 'Section 1',
href: '#section1'
}, {
text: 'Section 2',
href: '#section2'
}
]
}
];
Compiler:
$.each(nav, function() {
var obj = this,
src = obj.selector.html(),
template = Handlebars.compile(src),
html = template(obj.data);
obj.selector.html(html);
});
HB Helper (does not work - context is undefined):
Handlebars.registerHelper('each', function(context, options) {
var ret = "";
for(var i=0, j=context.length; i<j; i++) {
ret = ret + options.fn(context[i]);
}
return ret;
});
HB Helper (does work using this instead of context):
Handlebars.registerHelper('each', function(context, options) {
var ret = "";
for(var i=0, j=this.length; i<j; i++) {
ret = ret + options.fn(this[i]);
}
return ret;
});
Any helps is appreciated.

Before looking at the rest of the JS, can I point out that the JSON looks wrong.
Name, selector and data are overwritten the second time they occur in your JSON. If this is just because of some bits being omitted when pasting to SO, then do ignore me ;o)
But if that is the real JSON, then it needs changing before looking at any functional stuff
<script>
var nav = [
{
name: 'global',
selector: $('.global-nav'),
data: [
{
text: 'Page 1',
href: 'page1.html'
}, {
text: 'Page 2',
href: 'page2.html'
}
]
}, // this line
{ // and this line added
name: 'content',
selector: $('.content-nav'),
data: [
{
text: 'Section 1',
href: '#section1'
}, {
text: 'Section 2',
href: '#section2'
}
]
}
];
</script>

Related

Vue CKEditor get title and body separately

How do I get the title and text separately in Vue CKEditor? (like here)
this.editor.getTitle() - this.editor.getTitle is not a function
this.editor.getBody() - this.editor.getBody is not a function
this.editor.getTitle - undefined
this.editor.getBody - undefined
piece of code:
data() {
return {
editor: ClassicEditor,
editorData: '<p></p>',
editorConfig: {
plugins: [
Image,
ImageUpload,
ImageCaption,
AutoImage,
Title,
AutoSave,
Heading,
MediaEmbed,
HtmlEmbed,
EssentialsPlugin,
BoldPlugin,
ItalicPlugin,
LinkPlugin,
ParagraphPlugin,
],
extraPlugins: [cuteUploadAdapterPlugin],
toolbar: {
items: [
'Heading',
'bold',
'italic',
'link',
'undo',
'redo',
'ImageUpload',
'mediaEmbed',
'htmlEmbed'
]
},
heading: {
options: [
{model: 'paragraph', title: 'Paragraph', class: 'ck-heading_paragraph'},
{model: 'heading2', view: 'h2', title: 'Heading 2', class: 'ck-heading_heading2'},
{model: 'heading3', view: 'h3', title: 'Heading 3', class: 'ck-heading_heading3'}
]
},
title: {
placeholder: 'title'
},
placeholder: 'body',
},
};
},
methods: {
buttonClick() {
console.log(this.editor.getTitle())
console.log(this.editor.getBody())
It seems to work:
let getTitleAndBody = function () {
let data = {
title: '',
body: '',
}
const parser = new DOMParser();
const doc = parser.parseFromString(editorData, 'text/html');
const title = doc.getElementsByTagName('h1')[0]
data.title = title.innerText
doc.body.removeChild(title)
data.body = doc.body.innerHTML
return data;
}
let data = getTitleAndBody()
data.title // get title
data.body // get body
getTitle() and getBody() functions are not defined on editor level that's why you are getting undefined as response. It is defined on plugin Title which is defined in #ckeditor/ckeditor5-heading.
So, the correct way to get the body and title is
// for getting title
editor.plugins.get('Title').getTitle()
// for getting body of the document
editor.plugins.get('Title').getBody()

How to get a proper rendering of my node tree?

I want to build a DOM tree using a JS object (classic topic). I achieved most of the work but I am getting a non sense result: DOM tree appear to be created correctly BUT all nodes are rendered flatten on a single line and Input element is clipped.
I highly suspect the buildNode function not working correctly, but I can not find the flaw.
/**** FILTERS ****/
// TODO : change crappy filter using dictionnary
const attrOnly = (str) => !(str === 'content' || str === 'tag' || str === 'children');
/**** TESTS ****/
const hasChildren = (obj) => obj.hasOwnProperty('children'),
hasContent = (obj) => obj.hasOwnProperty('content');
// TODO: search for namespace given a tag name (SVG)
const findNameSpace = (str) => null;
// Build node with correct attributes and values
const buildNode = function (element, parent) {
const tag = (element.tag || 'div'),
tagAttr = Object.keys(element).filter(attrOnly),
node = document.createElementNS(findNameSpace(tag), tag);
tagAttr.forEach(
(attr) => node.setAttributeNS(findNameSpace(tag), attr, element[attr])
);
hasContent(element) ? node.innerHTML = element.content : null;
return parent.appendChild(node);
}
// Walk along the tree mapping current element with function f.
function walkTree(f, element, parent) {
const current = f(element, parent);
// Reccursively walk children, if necessary
(hasChildren(element) && element.children.forEach(
child => walkTree(f, child, current)
));
};
let tree = {
tag: 'div',
id: 'tree',
children: [{
tag: 'section',
id: 'section-l1-1',
class: 'l1',
content: 'Use <em>me</em> as I am, I am gorgeous!',
children: [{
tag: 'div',
id: 'div-l2',
class: 'l2',
children: [{
tag: 'p',
content: 'Here is a nested paragraph.'
}]
}, {
tag: 'form',
id: 'form-l2',
class: 'l2',
onsubmit: 'alert("Function called!");',
children: [{
tag: 'input',
type: 'text',
id: 'input-l3',
class: 'l3',
value: 'self-closing tag case!'
}]
}]
}, {
tag: 'footer',
id: 'end-page',
class: 'l1',
content: 'This is a running experiment.'
}]
};
walkTree(buildNode, tree, document.getElementById('seed'));
#seed div,form,input {
display: block;
}
<div id="seed"></div>
So I found that namespaces handling was my problem. As I wanted to get render HTML elements and SVG elements at the same time, I needed that...
I did not find a proper fix to that problem so I set lower expectations and only render HTML using document.createElement() (and Node.setAttribute()) instead of document.createElementNS() (and Node.setAttributeNS()).
To see how far I get with namespace handling:
/* DICTIONARIES */
const nonAttr = ['content', 'tag', 'children'],
// Search for tag's special namespace. Source: https://www.w3.org/TR/2011/WD-html5-20110525/namespaces.html
tagNamespace = {
'svg': 'https://www.w3.org/2000/svg'
};
/**** FILTERS ****/
const attrOnly = (str) => !(nonAttr.includes(str));
/**** TESTS ****/
const hasChildren = (obj) => obj.hasOwnProperty('children'),
hasContent = (obj) => obj.hasOwnProperty('content');
// Search for namespace given a tag name
const findNameSpace = (str) => (tagNamespace[str] || 'http://www.w3.org/1999/xhtml');
// Build node with correct attributes and values
const buildNode = function (element, parent) {
const tag = (element.tag || 'div'),
tagAttr = Object.keys(element).filter(attrOnly),
node = document.createElementNS(findNameSpace(tag), tag);
tagAttr.forEach(
(attr) => node.setAttribute(attr, element[attr])
);
hasContent(element) ? node.innerHTML = element.content : null;
return parent.appendChild(node);
}
// Walk along the tree mapping current element with function f.
function walkTree(f, element, parent) {
const current = f(element, parent);
// Reccursively walk children, if necessary
(hasChildren(element) && element.children.forEach(
child => walkTree(f, child, current)
));
};
let tree = {
tag: 'div',
id: 'tree',
children: [{
tag: 'section',
id: 'section-l1-1',
class: 'l1',
content: 'Use <em>me</em> as I am, I am gorgeous!',
children: [{
tag: 'div',
id: 'div-l2',
class: 'l2',
children: [{
tag: 'p',
content: 'Here is a nested paragraph.'
}]
}, {
tag: 'form',
id: 'form-l2',
class: 'l2',
onsubmit: 'alert("Function called!");',
action: '',
method: 'GET',
children: [{
tag: 'input',
type: 'text',
id: 'text-l3',
name: 'text-l3',
class: 'l3',
placeholder: 'self-closing tag case!'
},{
tag: 'input',
type: 'submit',
id: 'submit-l3',
name: 'submit-l3',
class: 'l3',
value: 'submit!'
}]
}]
}, {
tag: 'footer',
id: 'end-page',
class: 'l1',
content: 'This is a running experiment.',
children: [{
tag: 'svg',
class: 'l2',
children : [{
tag: 'rect',
width: '10',
height: '10',
fill: 'black'
}]
}]
}]
};
walkTree(buildNode, tree, document.getElementById('seed'));
.l1 {
background-color: DarkGrey;
}
.l2 {
background-color: Grey;
}
.l3 {
background-color: LightGrey;
}
.l4 {
background-color: White;
}
<div id="seed"></div>
To see working code without namespace support:
/* DICTIONARIES */
const nonAttr = ['content', 'tag', 'children'];
/**** FILTERS ****/
const attrOnly = (str) => !(nonAttr.includes(str));
/**** TESTS ****/
const hasChildren = (obj) => obj.hasOwnProperty('children'),
hasContent = (obj) => obj.hasOwnProperty('content');
// Build node with correct attributes and values
const buildNode = function(element, parent) {
const tag = (element.tag || 'div'),
tagAttr = Object.keys(element).filter(attrOnly),
node = document.createElement(tag);
tagAttr.forEach(
(attr) => node.setAttribute(attr, element[attr])
);
hasContent(element) ? node.innerHTML = element.content : null;
return parent.appendChild(node);
}
// Walk along the tree mapping current element with function f.
function walkTree(f, element, parent) {
const current = f(element, parent);
// Reccursively walk children, if necessary
(hasChildren(element) && element.children.forEach(
child => walkTree(f, child, current)
));
};
let tree = {
tag: 'div',
id: 'tree',
children: [{
tag: 'section',
id: 'section-l1-1',
class: 'l1',
content: 'Use <em>me</em> as I am, I am gorgeous!',
children: [{
tag: 'div',
id: 'div-l2',
class: 'l2',
children: [{
tag: 'p',
content: 'Here is a nested paragraph.'
}]
}, {
tag: 'form',
id: 'form-l2',
class: 'l2',
onsubmit: 'alert("Function called!");',
action: '',
method: 'GET',
children: [{
tag: 'input',
type: 'text',
id: 'text-l3',
name: 'text-l3',
class: 'l3',
placeholder: 'self-closing tag case!'
}, {
tag: 'input',
type: 'submit',
id: 'submit-l3',
name: 'submit-l3',
class: 'l3',
value: 'submit!'
}]
}]
}, {
tag: 'footer',
id: 'end-page',
class: 'l1',
content: 'This is a running experiment.',
// SVG CAN'T GET RENDERED BECAUSE NAMESPACES
children: [{
tag: 'svg',
class: 'l2',
children: [{
tag: 'rect',
fill: 'black'
}]
}]
}]
};
walkTree(buildNode, tree, document.getElementById('seed'));
.l1 {
background-color: DarkGrey;
}
.l2 {
background-color: Grey;
}
.l3 {
background-color: LightGrey;
}
.l4 {
background-color: White;
}
<div id="seed"></div>
PS. I have to say that I have dirt in my mouth...

AngularJS nested ng-repeat access $parent array element

Right so I have a dilemma, that seems like a simple question but I can't figure it out.
I have a nested array:
$scope.rootItem = {
id: '1',
type: 'course',
title: 'Adobe Photoshop CC for beginners',
items: [{
id: '2',
type: 'label',
title:'Label Title',
items:[{
id: '3',
type: 'module',
title:'Module title',
items: [{
id: '4',
type: 'topic',
title:'Topic title',
items: [{
id: '5',
type: 'content',
title:'Content title'
}, {
id: '6',
type: 'content',
title:'Content title'
}]
}]
},{
id: '7',
type: 'resources',
title:'Resources'
},{
id: '8',
type: 'module',
title:'Module title',
items: [{
id: '9',
type: 'topic',
title:'Topic',
items: [{
id: '10',
type: 'question',
title:'Question title'
}]
}, {
id: '11',
type: 'topic',
title:'Topic title',
items: [{
id: '12',
type: 'content',
title:'Content title'
}]
}]
}]
},{
id: '14',
type: 'assessmentLabel',
title: 'Assessment Label',
items: [{
id: '15',
type: 'assessment',
title: 'Assessment Title',
items: [{
id: '16',
type: 'courseAssessment',
title: 'Course Assessment Question',
items: []
}]
}]
}]
};
That is outputted using ng-repeat. All works great there, by the way it is also sortable using ng-sortable (based on JQuery UI Sortable).
What I'm trying to do is duplicate lets say id: 5 using angular.copy().
HTML:
<a href="" title="Duplicate Content" data-ng-click="duplicate(ngModelItem, $parent.ngModelItem.items)">
<span class="icon-duplicate"></span>
</a>
That seems to work fine too. I'm able to pass the object to the function.
The problem arises when I try and push that object to its parents array. I read about $parent and what I think would make sense is passing $parent.ngModelItems.items to the ng-click as such:
data-ng-click="duplicate(ngModelItem, $parent.ngModelItem.items)"
Which to me makes sense, pass parents ngModelItem.items (items is array that ID:5 is part of). But I can't figure out why do I get $parent.ngModelItem.items as undefined.
This is my controller:
$scope.duplicate = function(item, parent) {
var itemCopy = angular.copy(item);
parent.push(item);
};
HTML ng-repeat:
<ul class="apps-container" ui-sortable="sortableOptions" ng-model="ngModelItem.items" ng-class="ngModelItem.type">
<li class="innerCont" ng-repeat="innerItem in ngModelItem.items">
<tg-dynamic-directive ng-model="innerItem" tg-dynamic-directive-view="getView">
</tg-dynamic-directive>
</li>
</ul>
But angular seems to have different ideas. So I guess my question is how can I pass parents ngModelItem.items (rootItem.items) so that I can access that array?
Can someone please explain why {{$parent.$parent.ngModelItems.id}} returns correct parent id. Yet when I try to pass that parent to the function such as
data-ng-click="duplicate(parent.parent.ngModelItem.items)"
It doesnt work
Directive below:
angular.module('tg.dynamicDirective', [])
.directive('tgDynamicDirective', ['$compile',
function($compile) {
'use strict';
function templateUrlProvider(getView, ngModelItem) {
if (getView) {
if (typeof getView === 'function') {
var templateUrl = getView(ngModelItem) || '';
if (templateUrl) {
return templateUrl;
}
} else if (typeof getView === 'string' && getView.length) {
return getView;
}
}
return '';
}
return {
restrict: 'E',
require: '^ngModel',
scope: true,
template: '<div ng-include="templateUrl"></div>',
link: function(scope, element, attrs, ngModel) {
scope.$watch(function() {
var ngModelItem = scope.$eval(attrs.ngModel);
var getView = scope.$eval(attrs.tgDynamicDirectiveView);
scope.ngModelItem = ngModelItem;
return templateUrlProvider(getView, ngModelItem);
}, function(newValue, oldValue) {
scope.templateUrl = newValue;
});
}
};
}
]);
After few hours of trying to fix this, and reading numerous articles about $scope inheritance I found out that ng-if create new scope using prototypical inheritance. Which I was not accounting for.
Which required me to insert one more $parent when passing it to the function as such:
data-ng-click="duplicate(ngModelItem, $parent.$parent.$parent.ngModelItem)"
and then in the controller do something like this:
$scope.duplicate = function(item, parent) {
var itemCopy = angular.copy(item);
var parentArray = parent.items;
parentArray.push(itemCopy)
};
Hope this will save someone hours of work, whoever runs into this problem.

Creating a simple Sencha component using a loop

How do we substitute hard-coded data with a for-loop in Sencha ExtJS?
Say, for example I've defined the following list:
Ext.application({
launch: function() {
Ext.create('Ext.List', {
fullscreen: true,
itemTpl: '{title}',
data: [{
title: 'Item 1'
}, {
title: 'Item 2'
}, {
title: 'Item 3'
}, {
title: 'Item 4'
}]
});
}
});
How to replace data to something like this:
Ext.application({
launch: function() {
Ext.create('Ext.List', {
fullscreen: true,
itemTpl: '{title}',
data: [
for(int i=0;i<5;i++){
{title: 'Item '+i},
}
]
});
}
});
This is really basic stuff - I'd recommend you familiarise yourself with the basic language constructs before investing time in a framework. There are several ways you could do this...
see: MDN: A re-introduction to Javascript
Simplest way, create your configuration data first and assign it to a variable:
Ext.application({
launch: function() {
var listData = [];
for(var i=0;i<5;i++)
listData.push({title: 'Item '+i});
Ext.create('Ext.List', {
// ...
data: listData
});
}
});
... or for those times when you're in the global execution scope and you don't want to pollute the window object with unnecessary variables - or just have an OCD over "one-liners" - you could take advantage of an inline function / closure:
Ext.create('Ext.List', {
// ...
data: (function(){
var data = [];
for(var i=0;i<5;i++)
data.push({title: 'Item '+i});
return data;
})()
});
... or on occasion I've used the following because I think it looks neater (but that's subjective):
Ext.create('Ext.List', {
// ...
data: Ext.Array.map(Array.apply(null, Array(5)), function(o,i){
return {title: 'Item '+i};
})
});

ckeditor plugin dialog select get the description from the selected one

Im am developing a placeholder plugin to CKEDITOR and it´s basically complete. The problem I have is that I am trying to get the value and description from the select within the dialog and I am only getting the value.
The array that contains the description and value looks like this
-->
items: [['description1', 'value1'], ['description2', 'value2']] <--
In the return -> contents -> elements with ID dropdown I have setup and commit function. In these functions I need to get the description just like getting the name from select option.
Really need help with this one, thanks in advance
example -->
<select>
<option value="value1">description1</option>
<option value="value2">description2</option>
</select>
example <--
(function () {
function placeholderDialog(editor, isEdit) {
var lang = editor.lang.phlink,
generalLabel = editor.lang.common.generalTab;
return {
title: lang.title,
minWidth: 300,
minHeight: 80,
contents: [
{
id: 'info',
label: generalLabel,
title: generalLabel,
elements: [
{
id: 'dropdown'
, type: 'select'
, label: lang.chooseVal
, 'default': 'Detta är default'
, items: [['description1', 'value1'], ['description2', 'value2']]
, setup: function (data) {
// need the description
this.setValue(data.title);
}
, commit: function (data) {
// need the description
data.title = this.getValue();
}
},
{
id: 'text',
type: 'text',
style: 'width: 100%;',
label: lang.text,
'default': '',
required: true,
validate: CKEDITOR.dialog.validate.notEmpty(lang.textMissing),
setup: function (data) {
this.setValue(data.text);
},
commit: function (data) {
data.text = this.getValue();
}
}
]
}
],
onShow: function () {
var data = { tag: 'link', content: "detta är innehåll", title: "Länk till svar", text: "detta är text" };
if (isEdit) {
this._element = CKEDITOR.plugins.phlink.getSelectedPlaceHolder(editor);
data.title = this._element.getAttribute('title');
data.text = this._element.getText();
data.tag = this._element.getAttribute('data-jztag');
}
this.setupContent(data);
},
onOk: function () {
var data = { tag: 'link', content: null, title: null, text: null };
this.commitContent(data);
CKEDITOR.plugins.phlink.createPlaceholder(editor, this._element, data);
delete this._element;
}
};
}
CKEDITOR.dialog.add('createplaceholder', function (editor) {
return placeholderDialog(editor);
});
CKEDITOR.dialog.add('editplaceholder', function (editor) {
return placeholderDialog(editor, 1);
});
})();
Use the following to get option's text:
var input = this.getInputElement().$;
console.log( input.options[ input.selectedIndex ].text );
>> "description1"

Categories