I am working on creating a Polymer app for a pet project, using the Polymer Starter Kit, and modifying it to add horizontal toolbar, background images, etc. So far, everything has worked fine except the links in the app-toolbar do not update the "view" when I click on them.
All my debugging so far points me in the direction of the "page" property. I believe this is not getting updated or is null, causing the view to default to "about" (which is View-2 as per the starter kit) as specified in the _routePageChanged observer method.
I tried using the debugger on DevTools on Chrome, but being new to this, I'm not very clear if I did it correctly. I just kept going in and out of hundred of function calls.
I am copying relevant parts of the app-shell.
Please help or at least point me in the right direction; I've been trying to fix this since 2 days. Thank you!
<app-location
route="{{route}}">
</app-location>
<app-route
route="{{route}}"
pattern=":view"
data="{{routeData}}"
tail="{{subroute}}">
</app-route>
<!-- Main content -->
<app-header-layout has-scrolling-region>
<app-header slot="header" class="main-header" condenses effects="waterfall">
<app-toolbar class="logo"></app-toolbar>
<app-toolbar class="tabs-bar" hidden$="{{!wideLayout}}">
<paper-tabs selected="[[selected]]" attr-for-selected="name">
<paper-tab>Home</paper-tab>
<paper-tab>About Us</paper-tab>
<paper-tab>Pricing</paper-tab>
</paper-tabs>
</app-toolbar>
</app-header>
<iron-pages
selected="[[page]]"
attr-for-selected="name"
fallback-selection="view404"
role="main">
<my-view1 name="home"></my-view1>
<my-view2 name="about"></my-view2>
<my-view3 name="pricing"></my-view3>
<my-view404 name="view404"></my-view404>
</iron-pages>
</app-header-layout>
</app-drawer-layout>
<script>
class MyApp extends Polymer.Element {
static get is() { return 'my-app'; }
static get properties() {
return {
page: {
type: String,
reflectToAttribute: true,
observer: '_pageChanged'
},
wideLayout: {
type: Boolean,
value: false,
observer: 'onLayoutChange'
},
items: {
type: Array,
value: function() {
return ['Home', 'About', 'Pricing', 'Adults', 'Contact'];
}
},
routeData: Object,
subroute: String,
// This shouldn't be neccessary, but the Analyzer isn't picking up
// Polymer.Element#rootPath
// rootPath: String,
};
}
static get observers() {
return [
'_routePageChanged(routeData.page)',
];
}
_routePageChanged(page) {
// If no page was found in the route data, page will be an empty string.
// Default to 'view1' in that case.
this.page = page || 'about';
console.log('_routePageChange');
// Close a non-persistent drawer when the page & route are changed.
if (!this.$.drawer.persistent) {
this.$.drawer.close();
}
}
_pageChanged(page) {
// Load page import on demand. Show 404 page if fails
var resolvedPageUrl = this.resolveUrl(page + '.html');
Polymer.importHref(
resolvedPageUrl,
null,
this._showPage404.bind(this),
true);
}
_showPage404() {
this.page = 'view404';
}
_onLayoutChange(wide) {
var drawer = this.$.drawer;
if (wide && drawer.opened){
drawer.opened = false;
}
}
}
window.customElements.define(MyApp.is, MyApp);
</script>
Here's a snapshot of the page when I click on the "Home" link.
Snapshot of the page
I have fixed the same issue on my app, page observed functions like:
static get properties() { return {
page:{
type:String,
reflectToAttribute:true,
observer: '_pageChanged'},
...
_pageChanged(page, oldPage) {
if (page != null) {
if (page === "home" ) {
this.set('routeData.page', "");
} else {
this.set('routeData.page', page);
}
.......
}
}
Honestly, I am still trying to find the better solution. Because I have users page and I could not manage to able to indexed at google search results. This only keeps synchronized the iron-pages and address link.
Related
Background: Trying to use ckeditor5 as a replacement for my homegrown editor in a non-invasive way - meaning without changing my edited content or its class definitions. Would like to have WYSIWYG in the editor. Using django_ckeditor_5 as a base with my own ckeditor5 build that includes ckedito5-inspector and my extraPlugins and custom CSS. This works nicely.
Problem: When I load the following HTML into ClassicEditor (edited textarea.value):
<p>Text with inline image: <img class="someclass" src="/media/uploads/some.jpeg"></p>
in the editor view area, browser-inspection of the DOM shows:
...
<p>Text with an inline image:
<span class="image-inline ck-widget someclass ck-widget_with-resizer" contenteditable="false">
<img src="/media/uploads/some.jpeg">
<div class="ck ck-reset_all ck-widget__resizer ck-hidden">
<div ...></div></span></p>
...
Because the "someclass" class has been removed from and moved to the enclosing class attributes, my stylesheets are not able to size this image element as they would appear before editing.
If, within the ckeditor5 view, I edit the element using the browser inspector 'by hand' and add back class="someclass" to the image, ckeditor5 displays my page as I'd expect it with "someclass" and with the editing frame/tools also there. Switching to source-editing and back shows the class="someclass" on the and keeps it there after switching back to document editing mode.
(To get all this, I enabled the GeneralHtmlSupport plugin in the editor config with all allowed per instructions, and that seems to work fine.) I also added the following simple plugin:
export default class Extend extends Plugin {
static get pluginName() {
return 'Extend';
}
#updateSchema() {
const schema = this.editor.model.schema;
schema.extend('imageInline', {
allowAttributes: ['class']
});
}
init() {
const editor = this.editor;
this.#updateSchema();
}
}
to extend the imageInline model hoping that would make the Image plugin keep this class attribute.
This is the part where I need some direction on how to proceed - what should be added/modified in the Image Plugin or in my Extend plugin to keep the class attribute with the element while editing - basically to fulfill the WYSIWYG desire?
The following version does not rely on GeneralHtmlSupport but creates an imageClassAttribute model element and uses that to convert only the image class attribute and place it on the imageInline model view widget element.
import Plugin from '#ckeditor/ckeditor5-core/src/plugin';
export default class Extend extends Plugin {
static get pluginName() {
return 'Extend';
}
#updateSchema() {
const schema = this.editor.model.schema;
schema.register( 'imageClassAttribute', {
isBlock: false,
isInline: false,
isObject: true,
isSelectable: false,
isContent: true,
allowWhere: 'imageInline',
});
schema.extend('imageInline', {
allowAttributes: ['imageClassAttribute' ]
});
}
init() {
const editor = this.editor;
this.#updateSchema();
this.#setupConversion();
}
#setupConversion() {
const editor = this.editor;
const t = editor.t;
const conversion = editor.conversion;
conversion.for( 'upcast' )
.attributeToAttribute({
view: 'class',
model: 'imageClassAttribute'
});
conversion.for( 'dataDowncast' )
.attributeToAttribute({
model: 'imageClassAttribute',
view: 'class'
});
conversion.for ( 'editingDowncast' ).add( // Custom conversion helper
dispatcher =>
dispatcher.on( 'attribute:imageClassAttribute:imageInline', (evt, data, { writer, consumable, mapper }) => {
if ( !consumable.consume(data.item, evt.name) ) {
return;
}
const imageContainer = mapper.toViewElement(data.item);
const imageElement = imageContainer.getChild(0);
if ( data.attributeNewValue !== null ) {
writer.setAttribute('class', data.attributeNewValue, imageElement);
} else {
writer.removeAttribute('class', imageElement);
}
})
);
}
}
Well, Mr. Nose Tothegrind found two solutions after digging through ckeditor5 code, here's the first one. This extension Plugin restores all image attributes that are collected by GeneralHtmlSupport. It can be imported and added to a custom ckeditor5 build app.js file by adding config.extraPlugins = [ Extend ]; before the editor.create(...) statement.
import Plugin from '#ckeditor/ckeditor5-core/src/plugin';
import GeneralHtmlSupport from '#ckeditor/ckeditor5-html-support/src/generalhtmlsupport';
export default class Extend extends Plugin {
static get pluginName() {
return 'Extend';
}
static get requires() {
return [ GeneralHtmlSupport ];
}
init() {
const editor = this.editor;
this.#setupConversion();
}
#setupConversion() {
const editor = this.editor;
const t = editor.t;
const conversion = editor.conversion;
conversion.for ( 'editingDowncast' ).add( // Custom conversion helper
dispatcher =>
dispatcher.on( 'attribute:htmlAttributes:imageInline', (evt, data, { writer, mapper }) => {
const imageContainer = mapper.toViewElement(data.item);
const imageElement = imageContainer.getChild(0);
if ( data.attributeNewValue !== null ) {
const newValue = data.attributeNewValue;
if ( newValue.classes ) {
writer.setAttribute('class', newValue.classes.join(' '), imageElement);
}
if ( newValue.attributes ) {
for (const name of Object.keys(newValue.attributes)) {
writer.setAttribute( name, newValue.attributes[name], imageElement);
}
}
} else {
writer.removeAttribute('class', imageElement);
}
})
);
}a
}
I am working on moving the functionality of an existing Shopify theme over to the dawn theme as a base, I want to incorporate the new code in the same pattern as the Dawn theme so it will mean transferring most of the existing code to web components.
I am new to web components so I am basically just looking for some advice or feedback from the first task so I don't make a series of similar mistakes.
The feature is an infinite scroll type load more button for collection pages. It uses the pagination next page liquid object (stored in a data attribute of the custom element) to load the next page products with a fetch request and also updates the data attribute so the next page is loaded.
This was a simple function before but this now seems quite complicated and I was wondering if I have taken the right approach here.
{% javascript %}
class InfiniteScroll extends HTMLUListElement {
constructor() {
super();
}
connectedCallback() {
this.productsOnPage = document.querySelector('[is=infinite-scroll]');
this.loadProductSetHandler = this.onLoadProductSet.bind(this);
this.loadMoreButton = document.querySelector('.infinite-scroll__load-more-button');
this.loadMoreSpinner = document.querySelector('.infinite-scroll__load-more .loading-overlay__spinner');
this.loadMoreButton.addEventListener('click', this.loadProductSetHandler, false);
}
disconnectedCallback() {
this.loadMoreButton.removeEventListener();
}
getNextProductSet() {
return this.productsOnPage.dataset.nextUrl;
}
setNextProductSet(nextProductSetUrl) {
this.productsOnPage.dataset.nextUrl = nextProductSetUrl;
}
loadProductSet(nextProductSet) {
this.productsOnPage.append(...nextProductSet);
this.loadMoreButton.classList.remove('hidden');
this.loadMoreSpinner.classList.add('hidden');
}
loadingProcess() {
this.loadMoreButton.classList.add('hidden');
this.loadMoreSpinner.classList.remove('hidden');
}
async fetchNextProductSet() {
let nextProductSetInfo = {};
await fetch(this.getNextProductSet())
.then(function(response) {
return response.text();
})
.then(function(html) {
let newProductSet = new DOMParser().parseFromString(html, "text/html").querySelector('[is=infinite-scroll]');
nextProductSetInfo = { newProducts: newProductSet.children, nextProductSet: newProductSet.dataset.nextUrl};
})
.catch(function(err) {
console.log('Failed to fetch page: ', err);
});
this.setNextProductSet(nextProductSetInfo.nextProductSet);
this.loadProductSet(nextProductSetInfo.newProducts);
}
onLoadProductSet(e) {
e.preventDefault();
this.loadingProcess();
this.fetchNextProductSet();
}
}
customElements.define('infinite-scroll', InfiniteScroll, { extends: "ul" });
{% endjavascript %}```
I'm pretty new to Vue and Snotify, so please forgive the newb question. I've scanned the docs, and nothing jumps out at me.
Here's the deal: I have a Vue component that deletes files, using a Snotify confirm box. Like this:
destroy() {
this.$snotify.confirm('', 'Delete File?', {
buttons: [
{
text: 'Yes',
action: (toast) => {
axios.delete([API endpoint])
.then(response => {
// destroy the vue listeners, etc
this.$destroy();
// remove the element from the DOM
this.$el.parentNode.removeChild(this.$el);
});
this.$snotify.remove(toast.id);
}
},
{
text: 'No',
action: (toast) => {
this.$snotify.remove(toast.id)
}
}
]
})
}
The problem is that if you click the "Delete" button a second time, another "Delete File?" confirmation appears above the first. Expected behavior is that the second click make the confirmation go away.
Any help you can offer would be greatly appreciated.
this.$snotify.confirm() returns the toast info, which includes an ID that could be passed to this.$snotify.remove() for removal:
export default {
methods: {
destroy() {
if (this._toast) {
this.$snotify.remove(this._toast.id, true /* immediate */)
}
this._toast = this.$snotify.confirm('', 'Delete File?', {/*...*/})
}
}
}
demo
I'm working in VueJS. I'm trying to bind a class to one element based on an existence of a class on another element. The below is in a :for loop to print out the list.
The '#accordion-'+(index+1)) is the id of the div I want to check to see if a class exists on it.
I wrote a method and it works UNTIL I check the element's classList. Right now, I'm only doing a console log, but eventually this will return true and hopefully the class will apply.
methods: {
existingTopic: function(lcDivID) {
const element = document.querySelector(lcDivID);
console.log(element); //It gives me the element.
/* The below is where it crashes */
console.log(element.classList.contains("collapsePanelExistingTopic"));
}
}
I find it so frustrating. I've spent a day on this without any results. Any help you can provide it would be great.
Here it is, you can also use this.$el as document
...
methods: {
hasClass() {
const element = this.$el.querySelector('h1')
if (element.classList.contains('your-class-here')) {
console.log(true)
this.$el.querySelector('anotherelement').classList.add("your-another-class");
} else {
console.log(false)
}
}
},
mounted() {
this.hasClass()
}
...
Alternative
<h1 class="mb-5 fs-lg" ref="myElement">Sign up</h1>
...
methods: {
hasClass() {
const element = this.$refs.myElement
if (element.classList.contains('mb-5')) {
console.log(true)
this.$el.querySelector('anotherelement').classList.add("your-another-class");
} else {
console.log(false)
}
}
},
mounted() {
this.hasClass()
}
...
So you can define ref as :ref="index+1" in your loop
I am working on an Aikau Share Page where I a side bar that is using the Alfresco Share document library tree picker. The picker allows me to publish the nodeRef to another widget which will display information. I would like to use the tree view but i'm having trouble showing the documents and it is only showing the containers/folders. Anyone have any idea on what I need to do in order to solve this?
Here is the Aikua code i am using:
{
align: "sidebar",
name: "alfresco/layout/Twister",
config: {
label: "twister.library.label",
additionalCssClasses: "no-borders",
widgets: [
{
name: "alfresco/navigation/PathTree",
config: {
showRoot: true,
rootLabel: "Repository",
rootNode: "/app:company_home",
publishTopic: "ALF_ITEM_SELECTED"
}
}
]
}
}
I am wondering if I need to write an extension to the CoreXhr or what the steps would be in order to make this work.
Any help would be appreciated
I was able to figure this out. The problem comes from the repository script in the alfresco explorer side "treenode.get.js". The solution was to do the following
Create a new webscript in alfresco explorer and copy treenode.get.js code into the new webscript. I ended up calling mine customtreenode.get.js.
Remove the logic check for IsContainer in the newly created webscript
Create new Aikau file that extends PathTree. Here is the code below
define(["dojo/_base/declare",
"alfresco/navigation/PathTree",
"alfresco/documentlibrary/_AlfDocumentListTopicMixin",
"service/constants/Default",
"dojo/_base/lang",
"dojo/_base/array",
"dojo/dom-class",
"dojo/query",
"dojo/NodeList-dom"],
function(declare, PathTree, _AlfDocumentListTopicMixin, AlfConstants, lang, array, domClass, query) {
return declare([PathTree, _AlfDocumentListTopicMixin], {
useHash: true,
getTargetUrl: function alfresco_navigation_Tree__getTargetUrl() {
var url = null;
if (this.siteId && this.containerId)
{
url = AlfConstants.PROXY_URI + "slingshot/doclib/treenodeCustom/site/" + this.siteId + "/documentlibrary";
}
else if (this.rootNode)
{
url = AlfConstants.PROXY_URI + "slingshot/doclib/treenodeCustom/node/alfresco/company/home";
}
else if (!this.childRequestPublishTopic)
{
this.alfLog("error", "Cannot create a tree without 'siteId' and 'containerId' or 'rootNode' attributes", this);
}
return url;
}
});
});
Change your code to use the new CustomPathTree
{
align: "sidebar",
name: "alfresco/layout/Twister",
config: {
label: "twister.library.label",
additionalCssClasses: "no-borders",
widgets: [
{
name: "CustomWidgets/widgets/CustomTreeNode",
config: {
showRoot: true,
rootLabel: "Repository",
rootNode: "/app:company_home",
publishTopic: "ALF_ITEM_SELECTED"
}
}
]
}
}
It works after that. I still need to change the icons from folders to documents.