How to store tags in two separate collections that are in many-to-many relationship? - express

A user is allowed to create items, optionally add up to 10 tags. There's going to be a tag cloud on one of the pages, so storing them in a separate collection seems justified.
The Tag schema is modeled as following:
module.exports = mongoose.model("Tag", new mongoose.Schema({
label: {
type: String,
required: true,
lowercase: true,
maxLength: 35
}
}));
The Item schema has a tags key which references documents in Tag:
tags: {
type: [{ type: ObjectId, ref: "Tag" }],
validate: {
validator: n => n <= 10
}
},
Now, it would be easier to just store tags as subdocuments inside each item but as I said, I'll have a tag cloud later. Reading them from the items collection just to render the tag cloud will probably be a bit expensive, so duplicating them seems almost necessary and the lesser of the two devils. Please correct me if I'm wrong.
Anyway, the main question is: how do I add tags in this scenario then? The request content will be a JSON object, sent to /api/collections/items with the POST method. How can I make sure tags are added to both collections and the limit is respected? Because as it stands, the validator for tags in Item only checks the number of references stored, not the amount of tags assigned to an item. Am I wrong?
Ideally, the controller for adding items should be able to process such requests in a single operation:
async function addItem(req, res) {
try {
let item = await Item.create(req.body);
res.status(201).json(item);
} catch (err) {
res.status(400).json({ response: err.message });
}
}
Or am I going to have to first add tags and do something like item.tags.push(tags) afterwards?

Related

Is it possible to query document schema metadata in Sanity/GROQ?

I have a simple singleton document schema defined in my Sanity/NextJS project, to model my "Colophon" page (richText is a custom block field type):
export default {
title: 'Colophon',
name: 'colophon',
type: 'document',
__experimental_actions: ['update', 'publish'],
fields: [
{
title: 'Body',
name: 'body',
type: 'richText',
validation: Rule => Rule.required(),
},
],
};
I retrieve this document with a simple query in my NextJS application:
export async function getStaticProps() {
const colophon = await client.fetch(`
*[_type == "colophon"][0]
`);
// ...
};
Is it possible to write a GROQ query to retrieve the meta title defined in the schema, i.e. Colophon? Although this is a singleton document, I would like to avoid repeating this string in my project if possible. At the moment, I can only see the fields on the document in my results, i.e. body.
Thanks for reading!
No, I don't believe there is.
As far as I understand what you're after; The schema is defined in the studio-instance and not the datastore. Those two are not hard coupled. I have several studio-instances with small variations on the schemas using one single project/datastore. The API you query to get data does not care which studio and schema was used and cant answer for the actual schema details.

Vuex state structure and fetching when using same type of data with different values on different routes

I'm creating a portfolio with vue, vuex and vue-router that will show images.
On the homepage i will show images with 'show_home: true'.
Then there is "tag" pages (portfolio/:tagSlug) that will show images based on a slug, eg. 'weddings' with infinite scroll (auto populate pagination).
An image object will look something like:
{
id: 1,
title: 'Lorem..',
path: '..',
show_home: true
tags: [ {id: 1, slug: 'weddings'} ]
},
{
id: 2,
title: 'Lorem2..',
path: '..',
show_home: false
tags: [ {id: 1, slug: 'weddings'}, {id: 2, slug: 'water'} ]
}
Endpoints examples:
Homepage: GET: ../images/homepage?p=1
Tag page: GET: ../images/:slug?p=1
I can't figure out how I should structure this in vuex and handle the fetching..
Should i just create i single 'images: []' state and populate it with ALL the images after fetching them from the api in each route, then filter them with getters? How can i get the pagination in there in that case? Or do you have a better solution?
Thanks in advance
My preferred approach is to "flatten" the relationships and pull them as needed. This also allows you to only pull what you need from the server or related modules.
tags vuex module:
all: {
1: { <-- indexed by tag id
name: "weddings"
images: [1,2,3,4] <-- image ids
}
}
active: false <-- When there is an active tag, this becomes the id of the tag.
The vuex images module would follow this same pattern:
all: {
1: { <-- indexed by image id
title: 'Lorem..',
path: '..',
show_home: true
tags: [1,2,3,4] <-- tag ids
}
}
active: false <-- When there is an active image, this becomes the id of the image.
Then use a getter to hydrate the images or tags from the respective vuex module.
There is a great write up on this approach on this blog: https://medium.com/js-dojo/structuring-vuex-modules-for-relationships-speed-and-durability-de25f7403643
With this approach you will have fewer and smaller api calls, pagination is manageable and you don't need to worry about stale data in your relationships.
EDITED -- API info:
Two approaches come to mind.
1) always load the images with the tag.
Tag index request would not load any images, just the basic info for each tag.
When the user clicks on a tag, this inits an API call for the tag details:
Tag show request (tags/1 or tags/weddings) would return the tag with loaded relationships:
public function show($id)
{
$tag = Tag::where('id', $id)->with('images')->firstOrFail();
return new TagResource($tag); <-- will have all related images loaded.
}
2) set up a nested REST endpoint if needed
You can use the the resource controllers to shortcut the boilerplate like this:
api.php
Route::apiResource('tags.images', 'tags\TagImageController');
This route will watch your api calls and determine if it is index/store/show/delete. From your front end you can make a call like https://backendsite.com/tags/1/images (If wedding tag has an id of 1)
Then in the TagImageController you would have something like this:
public function index(Request $request, $id)
{
$tag = MemTag::find($id);
$images = $tag->images()->get();
$images->load(Image::allowedIncludes); <- or you can manually list relationships you want to load
return ImageResource::collection($images);
}

How to access the elements in a sencha touch 2 store

I am new to Sencha Touch so I am still struggling with the usage of stores.
I have created this store which I am successfully using to populate a list:
Ext.define('EventApp.store.events',{
extend: 'Ext.data.Store',
config: {
model: 'EventApp.model.event',
autoLoad: true,
storeId: 'events',
proxy:{
type:'ajax',
url: './resources/EventData.json',
reader: {
type: 'json',
rootProperty: 'events'
}
}
}
});
As I mentiones this store works correctly when referenced from a list and I can display the contents of it. Therefore I am assuming the store is correctly defined.
Unfortunately when I try to access the store from a controller for one of my views (which will be used to populate the items of a carousel) I don't seem to get any data back from the store. The code I am using is the following:
onEventCarouselInitialize : function(compon, eOptions) {
var past = compon.getPast();
var eventsStore = Ext.getStore('events');
eventsStore.each(function(record){
console.log('Record =',record); //<-- this never gets executed.
},this);
}
I have tried executing an eventsStore.load() on an eventsStore.sync() but I never seem to get any elements available in the store.
What am I missing?
Thanks
Oriol
What i have understand is, perhaps your store data has not been loaded when you are accessing it. So put you each() function on store inside this for delaying 500ms:
Ext.Function.defer(function(){
// Put each() here
}, 500);
Have a try by delaying more or less.

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'
}

insert in sencha touch data store

Could someone please explain how the insert works to add a record in a datastore
with tha fields: "title", "info" and "price"?
because i tried some things and none of them work. and the sencha website doesnt make it very clear.
Adding a new item to an existing Store isn't that hard actually.
First of you will need to configure your model and store. In your question you name the fields 'title, 'info' and 'price'.
Model:
Ext.regModel('myModel', {
fields: [
{name: 'id', type: 'int' },
{name: 'title', type: 'string' },
{name: 'info', type: 'string' },
{name: 'price', type: 'int' }
]
});
Next you configure the store that will hold the data, based on the above model. I think that, in your case, it should be a model without any data preloaded via, for example, JSON?
So lets make a localstorage (empty store). The Store consists of the model (myModel), you give it a storeID (so that you can later on reference the store by this ID). The proxy is localstorage and the unique ID of the Store will be the ID field of the Model.
Store:
var myStore = new Ext.data.Store({
model: "myModel",
storeId: "myStoreID",
proxy: {
type: "localstorage",
id: "id"
}
});
Now, suppose you have some kind of Form (in which the user can add input a title, info and price, and you want to add these items to the existing store on submittal.
Within the handler of the submittal button you now have to 'call' the store, and perform the add function on it. Within this add function you will have to define the params (the model params) and the data to insert.
Below I have used a mixture of fixed data and a variable to insert.
myStoreID.add({ title: "Mijn Titel", info: "Informatie, price: prijsvar });
The store will now be filled will now be filled with an extra data-record which you can use. Lets say for example that the store is attached to a dataview, then you can perform:
dataView.update();
The above isn't a full tutorial, but I think this will help you along?
Just an update of the YDL answer.
As per the dataView should be related to the updated store, the last sentence dataView.update() should not be needed, due to the automatic update of the views related to a store when it change.
new Ext.DataView({
store: MyStore,
itemSelector: 'div.thumb',
tpl: thumbTpl
});
later, if I do the following, the new item should be displayed in views (List, DataView, etc.) that have MyStore as store.
MyStore.add(newItem);
HTH.
Milton Rodríguez.
If you are trying to pass in an object that was returned from a getValue() on your form, make sure that you run a
myStore.sync();
after you have called the add() method, or you wont see it in your browsers local store.
It is Very easy try these
// first get those values and store in locally
var A_select1=Ext.getCmp('select1').getValue(); // get value
localStorage.setItem("Adult1_select1",A_select1); // set localStore
var AdultSalutation={
'Adult1_select1':A_select1, // assign the value
};
var AdultSalutationstore = Ext.getStore('Adult_AdultSalutationstore'); // get store
AdultSalutationstore.add(AdultSalutation); // add object
AdultSalutationstore.sync(); // sync
AdultSalutationstore.load(); // load