Dynamically update plugin view in ckeditor 5 - ckeditor5

I'm using ckEditor 5.
My aim is to create a simple search functionality to perform some ajax calls, allow the user to select from a result list and act accordingly (inserting link, or image, or media...).
The problem is that I want to dynamically update the plugin view. Something similar to this: https://github.com/ckeditor/ckeditor5/issues/520, but I'm using a dropdown in the toolbar (I started from the EmbedMedia plugin and didn't care too much about design for now).
I asked the same question on their git (https://github.com/ckeditor/ckeditor5/issues/1681)
My template is like:
this.setTemplate( {
tag: 'form',
attributes: {
class: [
'ck',
'ck-results-form'
],
tabindex: '-1'
},
children: [
this.searchInputView,
this.searchButtonView,
{
tag: 'div',
attributes: {
class: [ 'ck-result-grid' ]
},
children: this.items
},
this.saveButtonView,
this.cancelButtonView,
]
} );
// updateList method is like this:
updateList( valuesArray ) {
console.log(this.items);
this.items = new Array();
console.log(this.items);
for (let i = 0; i < valuesArray.length && i < 20; i++) {
const urlView = new TableSizeGridBoxView(i, valuesArray[i]);
urlView.on('select-url', () => {
this._selectedUrl = valuesArray[i];
});
this.items.push( urlView );
}
console.log(this.items);
//do something here to force refresh or render the section
}
I'm able to get
My expected result, after the search, is to get
and finally select the result.
Currently I can see the object correctly initialized and updated by doing console.log, but the view doesn't update... it stays blank as in the first image (that's why I think there must be something I should be able to put where I wrote //do something here to force refresh or render the section).
Furtermore I can tell that TableSizeGridBoxView is working properly because I'm able to display arbitrary values if I call the for loop in the initialization of the view.

Related

Performance issues with infinite scrolling and v-for

I’ve just recently started using Vue and so far so good, but I’ve ran into a bit of an issue that I can’t figure out a good solution to.
I have a photo gallery with a few different sections. I have an overall gallery component, a gallery section component and an image component. Essentially, I’m using a photos array for each section to store the photos data for that section. Within the sections I’m using v-for to display the photos. The gallery is infinitely scrolling so when you scroll to the bottom, more images load and the photos array for that section is updated.
Here’s my problem, currently the photos arrays are stored on the data of the overall gallery component, so when I update one of the photos arrays it seems to cause the entire gallery to rerender. The more images on the screen, the worse effect this has on the performance and the less responsive the page becomes.
I’m aware I could move the photos array to the data of the individual sections, but as far as I can tell this would still rerender that entire section.
I don’t really know if there’s any good solution that’ll do what I’m trying to do, having a certain amount of reactivity but only updating the things that changed. I don’t know if something like that is possible.
I’ve tried messing around with computed data, props, methods etc. but I can’t work out a better solution.
Here’s the code I’ve been working with in the overall gallery component:
<template>
<div class="photo-gallery">
<gallery-section v-for="(section, index) in sections" v-bind:section="section" class="photo-gallery__section" v-bind:key="index"></gallery-section>
</div>
</template>
<script>
import * as qb from "../queryBuilder";
let layout = [
{
title: "Highlighted Photos",
request: {
filters: qb.group([
qb.filter("rating", ">=", 4),
]),
options: {
offset: 0,
limit: 2,
order: ["rand()"],
size: 740
}
},
total: 2,
photoClass: "photo--highlighted",
loading: false,
photos: []
},
{
title: "More photos",
request: {
filters: qb.group([
qb.filter("rating", ">=", 2),
]),
options: {
offset: 0,
limit: 40,
order: ["rand()"]
}
},
total: Infinity,
loading: false,
photos: []
}
];
export default {
data() {
return {
sections: layout,
currentSection: 0
}
},
mounted() {
this.getPhotos(this.sections[0]);
this.getPhotos(this.sections[1]);
},
methods: {
getPhotos(section) {
section.loading = true;
let currentSection = this.currentSection;
fetch(`api/photos/search/filter/${JSON.stringify(section.request.filters)}/${JSON.stringify(section.request.options)}`)
.then(response => response.json())
.then(response => {
section.photos.push(...response.images);
// Set offset for next request
this.sections[this.currentSection].request.options.offset = section.photos.length;
// Check if current section is complete or if less than the requested amount of photos was returned
if (
this.sections[this.currentSection].total === section.photos.length ||
response.images.length < this.sections[this.currentSection].request.options.limit
) {
if (this.sections.length -1 != this.currentSection) {
// Move onto next section
this.currentSection++;
} else {
// Set currentSection to null if all sections have been fully loaded
this.currentSection = null;
}
}
})
.finally(() => {
section.loading = false;
});
},
scrollHandler() {
if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight - 500) {
if (this.currentSection != null && !this.sections[this.currentSection].loading) {
this.getPhotos(this.sections[this.currentSection]);
}
}
}
},
created() {
window.addEventListener("scroll", this.scrollHandler);
},
destroyed() {
window.removeEventListener("scroll", this.scrollHandler);
}
}
</script>
One thing I've also noticed is that whenever more photos are loaded, the mount function for every photo component on the page runs.
Would anyone be able to point me in the right direction? Any advise would be very much appreciated.
Thank you, Jason.
The issue was the the way I was generating the keys for my photos component instances, which cannot be seen in the code I included above. I figured out that the random number being generated as the key meant Vue could not keep track of the element as the key would keep changing. I'm now generating unique keys on the server side and using them instead. It works as expected now.

dgrid inside ContentPane - Scroll error

I have a problem with dgrid.... I have an AccordionContainer, and in each ContentPane of it,
I place a dgrid. The problems with the dgrid are:
1- Error with scroll: when scrolling down, in certain moment the scroll "skips" and jumps into the end and there's no way to scroll up and show the first records.
(I have seen in Firebug the error TypeError: grid._rows is null when the scroll fails).
2- Trying to change a value: sounds like no dgrid-datachange event is emitted,
no way to capture the event after editing a value.
I think these errors has to do with having dgrid inside layouts (dgrid inside ContentPane, inside AccordionContainer). I also included the DijitRegistry extension but even with this extensions I can't get
rid of this errors.
I have prepared this fiddle which reproduces the errors:
https://jsfiddle.net/9ax3q9jw/5/
Code:
var grid = new (declare([OnDemandGrid, DijitRegistry,Selection, Selector, Editor]))({
collection: tsStore,
selectionMode: 'none',
columns:
[
{id: 'timestamp', label:'Timestamp', formatter: function (value,rowIndex) {
return value[0];
}},
{id: 'value', label: 'Value',
get: function(value){
return value[1];
},
editor: "dijit/form/TextBox"
}
],
showHeader: true
});
grid.startup();
grid.on('dgrid-datachange',function(event){
alert('Change!');
console.log('Change: ' + JSON.stringify(event));
});
//Add Grid and TextArea to AccordionContainer.
var cp = new ContentPane({
title: tsStore.name,
content: grid
},"accordionContainer");
Any help will be appreciated,
Thanks,
Angel.
There are a couple of issues with this example that may be causing you problems.
Data
The store used in the fiddle is being created with an array of arrays, but stores are intended to work with arrays of objects. This is the root of the scrolling issue you're seeing. One property in each object should uniquely identify that object (the 'id' field). Without entry IDs, the grid can't properly keep track of the entries in your data set. The data array can easily be converted to an object array with each entry having timestamp and value properties, and the store can use timestamp as its ID property (the property it uses to uniquely identify each record).
var records = [];
var data = _globalData[0].data;
var item;
for (var i = 0; i < data.length; i++) {
item = data[i];
records.push({
timestamp: item[0],
value: item[1]
});
}
var tsStore = new declare([Memory, Trackable])({
data: records,
idProperty: 'timestamp',
name: 'Temperature'
});
_t._createTimeSeriesGrids(tsStore);
Setting up the store this way also allows the grid column definitions to be simplified. Using field names instead of IDs will allow the grid to call formatter functions with the corresponding field value for each row object.
columns: [{
field: 'timestamp',
label: 'Timestamp',
formatter: function (value) {
return value;
}
}, {
field: 'value',
label: 'Value',
formatter: function (value) {
return value;
},
editor: "dijit/form/TextBox"
}],
Loading
The fiddle is using declarative widgets and Dojo's automatic parsing functionality to build the page. In this situation the loader callback does not wait for the parser to complete before executing, so the widgets may not have been instantiated when the callback runs.
There are two ways to handle this: dojo/ready or explicit use of the parser.
parseOnLoad: true,
deps: [
...
dojo/ready,
dojo/domReady!
],
callback: function (..., ready) {
ready(function () {
var _t = this;
var _globalData = [];
...
});
}
or
parseOnLoad: false,
deps: [
...
dojo/parser,
dojo/domReady!
],
callback: function (..., parser) {
parser.parse().then(function () {
var _t = this;
var _globalData = [];
...
});
}
Layout
When adding widgets to containers, use Dijit's methods, like addChild and set('content', ...). These typically perform actions other than just adding a widget to the DOM, like starting up child widgets.
var cp = new ContentPane({
title: tsStore.name,
content: grid
});
registry.byId('accordionContainer').addChild(cp);
instead of
var cp = new ContentPane({
title: tsStore.name,
content: grid
}, "accordionContainer");
In the example code a ContentPane isn't even needed since the dgrid inherits from DijitRegistry -- it can be added directly as a child of the AccordionContainer.
This will also call the grid's startup method, so the explicit call in the code isn't needed.
registry.byId('accordionContainer').addChild(grid);
It also often necessary to re-layout the grid's container once the grid has been initially rendered to ensure it's properly sized.
var handle = grid.on('dgrid-refresh-complete', function () {
registry.byId('accordionContainer').resize();
// only need to do this the first time
handle.remove();
});

Nested List refresh

Any one knows how to refresh a nested list?
I am creating a list. On tap of an item it loads a nested list. if i traverse in a nested list and then tap to another item and come back to the prev item, its still stuck in the place where it was last left.
I tried using
Ext.getCmp('nestedList').refresh();
but thet doesn seem to work.
Below is my code for the nested list panel.
Ext.define("InfoImage.view.nestedList", {
extend:'Ext.NestedList',
xtype:'nestedList',
id:'nestedList',
config:{
fullscreen:'true',
title:'Work Items Task 1',
// ui:'normal',
xtype:'nestedList',
displayField : 'text',
store:'nestedListStore',
style: {
'background-color': 'rgba(0,140,153,0.1)'
}
}
});
and the code for loading the nested list is:
showListDetail : function(view, index, item, record) {
var rec = view.getStore().getAt(index);
// Ext.getStore('workItemStore').add({Task: '7'
// },{Server: 'mobcomp1'});
if (index == 0) {
this.getDocPanel().animateActiveItem(
this.getNestedPanel(), {
type : 'slide',
direction : 'up',
duration : 250
});
Ext.getCmp('nestedList').reload();
}
Any help is appreciated.
Thanks in advance.
You can't directly refresh a nestedlist. There's no refresh() or reload() method for it.
All you need to do is load the store which is used for your nestedlist using it's configured proxy.
this.store.setProxy({
// proxy configurations ...
// ......
// ......
});
this.store.load();

update a nested list in sencha touch 2

I'm having trouble redrawing my nested list with sencha touch 2. My code looks something like the following;
var data = {items:[{text:'hello', leaf:true}]};
Ext.application({
name:'main',
launch:function(){
Ext.define('ListItem', {
extend: 'Ext.data.Model',
config: {
fields: ['text']
}
});
var treeStore = Ext.create('Ext.data.TreeStore', {
id: 'mystore',
model: 'ListItem',
defaultRootProperty: 'items',
root: data});
Ext.create('Ext.NestedList', {
id:'mylist',
fullscreen: true,
store: treeStore
});
} // end launch:function()
}); // end Ext.application
During run time, I modify the data variable like so data.items[0].text = 'bye'. How do I get the nestedlist to refresh and show bye? I tried the following but none of them work:
var mystore = Ext.data.StoreManager.lookup('mystore');
mystore.setRoot(data);
Ext.getCmp('mylist').refresh(); // refresh, update, dolayout, repaint etc... does not exist.
Ext.getCmp('mylist').bindstore(mystore); // bindstore is deprecated
you should change data through record/store instances only then the Ext.NestedList will be automatically updated
var record = treeStore.getAt(0);
record.set('text', 'bye');
Working with data within a store is to be done using getById() and getAt(), which return Models that implement NodeInterface. Add data via appendChild, and the rest is self explanatory from there. The id value can be overridden in your data to make navigating the tree easier.
getStore().getById('myFirstLevelId').getById('mySecondLevelId').getById('myThirdLevelId')
http://docs.sencha.com/touch/2-0/#!/api/Ext.data.NodeInterface

sencha list paging plugin

I'm trying to use sencha touch's listpaging plugin. But there is almost no good( or bad ) documentation about how to use it and i'm confused.
When i activate the plugin in my list
this.myList = new Ext.List({
store: this.myStore,
plugins: [{
ptype: 'listpaging',
autoPaging: false
}],
itemTpl: '...'
});
a 'Load More' text and a loading image is added to the end of the list.
But i don't know how to configure my store to enable this plugin to be able to load more data.
App.regStore('MyStore', {
model: 'myModel',
proxy: {
type: 'ajax',
url: 'http://mydomain.com/json?howmany=10&page=1',
reader: {
type: 'json',
root: 'results'
}
}
});
App.stores.myStore = Ext.StoreMgr.get('MyStore');
How can i configure my store so when i tap "Load more", the store brings up page 2 and add them to the list automatically?
I've had a similar issue with the ListPaging plugin in SenchaTouch 2, and managed to sort out the 'load more' message behaviour when the end of the data set is reached.
This builds on what John Gordon has come up with (regarding specifying the pageSize and clearOnPageLoad config options), since these properties seem to be the same in Senchatouch 2. I haven't looked at SenchaTouch 1.x in detail. In SenchaTouch 2, all config options must be defined in a config property:
Ext.define('MessagingApp.store.MessageThreads', {
extend : 'Ext.data.Store',
requires: ['MessagingApp.model.MessageThread'],
config:
{
model: 'MessagingApp.model.MessageThread',
autoLoad: false,
clearOnPageLoad: false, // This is true by default
pageSize: 10, // This needs to be set for paging
proxy: {
type: 'jsonp',
pageParam: 'currentPage',
limitParam: 'pageSize',
url: APIURL + '/message-app-service/GetMessageThreads',
reader: {
type: 'json',
rootProperty: 'Data'
}
}
}
});
In the view, where we specify the plugins, we can override the 'load more' and 'no more records' messages:
{
xtype:'dataview',
store:'MessageThreads',
id:'threadList',
itemTpl:Ext.create('Ext.XTemplate',
'<!-- template markup goes here -->',
{
//custom helper functions here
}
),
plugins:[
{
xclass:'Ext.plugin.ListPaging',
autoPaging: true,
// These override the text; use CSS for styling
loadMoreText: 'Loading...',
noMoreRecordsText: 'All messages loaded'
}
]
}
The issue is that while our web service returns an array of records for a particular page, there is no overall total count property, which is needed for the plugin to determine when all records have been loaded. Hence as it is, the 'Load more' message will remain (issue #1 in the accepted solution). So in the init function of our controller, we have to attach an event handler for the load event on the store to hook into when a new page of data is received:
Ext.define('MessagingApp.controller.Messages',
{
extend: 'Ext.app.Controller',
config:
{
views: [
'MessageThreads',
// Other views referenced by this controller
],
stores: [
'MessageThreads'
],
refs:
{
// References to UI elements by selector...
}
},
init: function() {
// Other internal initialisation...
this.control(
{
// Selector-object pairs...
});
// Provide a means to intercept loads of each page of records
var threadStore = Ext.getStore('MessageThreads');
threadStore.addBeforeListener('load', this.checkForThreadListEnd, this);
},
// Remaining controller functions...
});
In the handler, we realise that we've reached the end of the data set when the number of records returned is less than the page size. If the total record count is a multiple of the page size, the last 'page' will have an empty array. Once the end of the data set has been identified, we update the totalCount config property of the store:
checkForThreadListEnd: function(store, records, isSuccessful) {
var pageSize = store.getPageSize();
var pageIndex = store.currentPage - 1; // Page numbers start at 1
if(isSuccessful && records.length < pageSize)
{
//Set count to disable 'loading' message
var totalRecords = pageIndex * pageSize + records.length;
store.setTotalCount(totalRecords);
}
else
store.setTotalCount(null);
},
// Other controller functions...
Because this is a 'before' event handler, this property will be crucially updated before the plugin decides whether to display the 'load more' or 'no more records' messages. Unfortunately, the framework doesn't provide a means to hide the 'no more records' message, so this would have to be done via CSS.
Hope this helps.
I'm having problems finding good documentation, too, but I can at least answer your question. You need to add pageSize to your store, clearOnPageLoad as well, if you want to not clear out what was already loaded. Her's my code:
Ext.regStore('publicresources', {
model: 'PublicResource',
autoLoad:false,
remoteFilter:true,
sortOnFilter:true,
pageSize: 15,
clearOnPageLoad: false,
sorters: [
{
property : 'distance',
direction: 'ASC'
}
]
});
My outstanding issues that I'm looking into are:
How to turn off the "More" element when there are no more records to load
How to detect that there are no more records to load, and where to put that detection code.
How to keep the list at the location that the user was at. Each load jumps back to the 1st item in the list
Good luck!
Remember also that this works only server side currently.
Forum thread http://www.sencha.com/forum/showthread.php?105193-Store-pageSize
In regards to the "load more vs. no more records" message -
If you are writing a custom proxy (example here A Sencha Touch MVC application with PhoneGap), you set the total records in the returned Operation.
If the total records are not yet known, you can do something like the below, where,
1) if you are returning the requested limit of records, set the total to something larger than the records the store will now hold
2) if returning < the requested limit of records, set the total to 1 (to force the "no more records message")
//return model instances in a result set
operation.resultSet = new Ext.data.ResultSet({
records: contacts,
//total : contacts.length,
total : contacts.length === operation._limit ? (operation._limit * operation._page +1) : 1,
loaded : true
});
//announce success
operation.setSuccessful();
operation.setCompleted();
//finish with callback
if (typeof callback == "function") {
callback.call(scope || thisProxy, operation);
}
Just to add what worked for me..
If your server returns a totalCount and you want to set it you can use the totalProperty in the reader
reader: {
type: 'json',
rootProperty: 'results',
totalProperty: 'resultCount'
}