Hi, everybody
I have a page index.html
The source code, I took from here
I have one file story.html - AMP-story that contains 6 AMP-pages
How can I specify the path to the AMP-page in the AMP-history
I read this docs
I tried /stories/knife1/story.html/#page-1
and /stories/knife1/story/#page-1
and /stories/knife1/story.html/page-1
But it doesn't work
The first AMP-page always opens and playback continues
In my index.html, I have this code
<div class="entry-point-container">
<div class="circular-entry-point">
<div class="entry-points">
<div class="entry-point-card-container">
<img src="img/products/new/knife1/photo_2021-06-05_16-31-08.jpg" style="border-color:#FF6F32">
<span class="entry-point-card-title">Q&A with ZOE Newman</span>
</div>
<div class="entry-point-card-container">
<img src="img/products/new/knife1/photo_2021-06-05_16-39-29.jpg" style="border-color:#EF7E31">
<span class="entry-point-card-title">24 Hours in New York City</span>
</div>
<div class="entry-point-card-container">
<img src="img/products/new/knife1/photo_2021-06-05_16-39-31.jpg" style="border-color:#EF7E31">
<span class="entry-point-card-title">The Next King of the Sea</span>
</div>
<div class="entry-point-card-container">
<img src="img/products/new/knife1/photo_2021-06-05_16-40-09.jpg" style="border-color:#EF7E31">
<span class="entry-point-card-title">Spark a Passion for Reading</span>
</div>
<div class="entry-point-card-container">
<img src="img/products/new/knife1/preview1.png" style="border-color:#FF6F32">
<span class="entry-point-card-title">Spark a Passion for Reading</span>
</div>
<div class="entry-point-card-container">
<img src="img/products/new/knife1/preview2.png" style="border-color:#EF7E31">
<span class="entry-point-card-title">The Next King of the Sea</span>
</div>
</div>
</div>
</div>
{
"behavior": {
"autoplay": false,
"pageScroll": false
},
"controls": [{
"name": "close",
"position": "start"
}]
}
And I use this JS code. Maybe this is the case?
const player = document.body.querySelector("amp-story-player");
const lightboxEl = document.querySelector(".lightbox");
if (player.isReady) {
initializeCarousel();
} else {
player.addEventListener("ready", () => {
initializeCarousel();
});
}
function initializeCarousel() {
const stories = player.getStories();
const thumbnails = document.querySelectorAll(
".entry-point-card-container img"
);
console.log(thumbnails);
thumbnails.forEach((img, idx) => {
img.addEventListener("click", () => {
player.show(stories[idx].href);
player.play();
lightboxEl.classList.toggle("show");
});
});
}
player.addEventListener("amp-story-player-close", () => {
player.pause();
lightboxEl.classList.toggle("show");
});
player.show() takes as the second parameter the pageId, so you can call player.show("/stories/knife1/story.html", "page-1") and it should show the page with the ID page-1. Outside of the player, stories support the URL hashParam #page=page-1 that also loads that page.
Related
Sorry for the long post, but I tried explaining things in as much detail as possible.
So as I dive deeper into JavaScript and start learning more and more about AJAX requests and other components, I've stumbled across something that I can't seem to figure out.
So below, I will explain what I'm doing and what I would like to do, and see if someone has some guidance for me.
So here is my Vue.js app:
new Vue({
name: 'o365-edit-modal',
el: '#o365-modal-edit',
data: function() {
return {
list: {},
}
},
created() {
this.fetchApplicationsMenu();
},
methods: {
fetchApplicationsMenu() {
var self = this;
wp.apiRequest( {
path: 'fh/v1/menus/applications',
method: 'GET',
}).then(menu => self.list = menu.data);
},
changed() {
const selected = this.$data.list.selected;
function get_ids(list, field) {
const output = [];
for (let i=0; i < list.length ; ++i)
output.push(list[i][field]);
return output;
}
const result = get_ids(selected, "id");
wp.apiRequest( {
path: 'fh/v1/menus/applications',
method: 'PUT',
data: {
ids: result,
},
}).then((post) => {
return post;
},
(error) => {
console.log(error);
});
},
add(x) {
this.$data.list.selected.push(...this.$data.list.available.splice(x, 1));
this.changed();
},
remove(x) {
this.$data.list.available.push(...this.$data.list.selected.splice(x, 1));
this.changed();
},
},
});
Then here is the HTML portion that I'm using to render the two columns:
<div class="column is-half-desktop is-full-mobile buttons">
<nav class="level is-mobile mb-0">
<div class="level-left">
<div class="level-item is-size-5 has-text-left">Selected</div>
</div>
<div class="level-right">
<div class="level-item">
<i class="fas fa-sort-alpha-up is-clickable"></i>
</div>
</div>
</nav>
<hr class="mt-1 mb-3">
<draggable class="list-group"
v-model="list.selected"
v-bind="dragOptions"
:list="list.selected"
:move="onMove"
#change="changed">
<button class="button is-fullwidth is-flex list-group-item o365_app_handle level is-mobile" v-for="(app, index) in list.selected" :key="app.id">
<div class="level-left">
<span class="icon" aria-hidden="true">
<img :src="app.icon_url" />
</span>
<span>{{app.name}}</span>
</div>
<div class="level-right">
<span class="icon has-text-danger is-clickable" #click="remove(index)">
<i class="fas fa-times"></i>
</span>
</div>
</button>
</draggable>
</div>
<div class="column is-half-desktop is-full-mobile buttons">
<div class="is-size-5 has-text-left">Available</div>
<hr class="mt-1 mb-3">
<draggable class="list-group"
v-model="list.available"
v-bind="dragOptions"
:list="list.available"
:move="onMove">
<button class="button is-fullwidth is-flex list-group-item o365_app_handle level is-mobile" v-for="(app, index) in list.available" :key="app.id">
<div class="level-left">
<span class="icon" aria-hidden="true">
<img :src="app.icon_url" />
</span>
<span>{{app.name}}</span>
</div>
<div class="level-right">
<span class="icon has-text-primary is-clickable" #click="add(index)">
<i class="fas fa-plus"></i>
</span>
</div>
</button>
</draggable>
</div>
That outputs the following items, and all works great. See the video display below of each component working as needed. This all works great! I'm calling the changed() method on add and remove which grabs all the IDs and stores them in the DB via an endpoint.
The Problem:
Now I have the following dropdown menu, which depends on the fh/v1/menus/applications endpoint to pull in all the items as shown below:
As you can see below, when I open the dropdown, it has three apps, when I open the cog wheel and remove one of the apps and it saves it but the dropdown doesn't get automatically updated, I have to refresh the page and then I will see the updates.
Does anyone know how to fetch the new items without a refresh?
Here is the HTML and the JS for the dropdown piece:
HTML: As you can see in there, I have data-source="applications" which pulls in the items inside the init_menu as shown in the JS.
<div class="dropdown-menu" id="dropdown-o365" role="menu">
<div class="dropdown-content">
<div class="container is-fluid px-4 pb-4">
<?php if ($application = Applications::init()): ?>
<div class="columns">
<div class="dropdown-item column is-full has-text-centered is-size-6">
<div class="level is-mobile">
<div class="level-left">
<?= $application->get_name() ?>
</div>
<div class="level-right">
<a class="navbar-item modal-element icon" id="o365-apps-cogwheel" data-target="o365-modal-edit" aria-haspopup="true">
<i class="fa fa-cog"></i>
</a>
</div>
</div>
</div>
</div>
<div class="columns is-multiline" data-source="applications"></div>
<?php else: ?>
<div class="columns">
<div class="column is-full">
No applications present.
</div>
</div>
<?php endif; ?>
</div>
</div>
</div>
Then here is the JavaScript. I initilize the method inside DOMContentLoaded using init_menu('applications');:
function init_menu(paths)
{
paths.forEach(path => {
const target = document.querySelector('[data-source=' + path + ']');
if (target) {
wp.api.loadPromise.done(function () {
const Menus = wp.api.models.Post.extend({
url: wpApiSettings.root + 'fh/v1/menus/' + path,
});
const menus = new Menus();
menus.fetch().then(posts => {
// This returns the data object.
const data = posts.data;
let post_list;
// Check if it's an array and see if selected is empty otherwise show available.
if (Array.isArray(data.selected) && data.selected.length !== 0) {
post_list = data.selected;
} else {
post_list = data.available;
}
post_list.forEach(function (post) {
switch(path) {
case 'applications':
target.appendChild(create_apps_dom_tree(post));
break;
default:
console.log('Path route is invalid.');
break;
}
})
})
})
}
});
}
function create_apps_dom_tree(post) {
const {
icon_url,
url,
name,
} = post
const container = document.createElement('div');
container.className = 'column is-one-third is-flex py-0';
const anchor = document.createElement('a');
anchor.href = url;
anchor.className = 'dropdown-item px-2 is-flex is-align-items-center';
const figure = document.createElement('figure');
figure.className = 'image is-32x32 is-flex';
const img = document.createElement('img');
img.src = icon_url;
const span = document.createElement('span');
span.className = 'pl-2';
span.textContent = name;
figure.appendChild(img);
anchor.append(figure, span);
container.appendChild(anchor);
return container;
}
If anyone has some guidance or an answer on how to pull in live data from the database on the fly, that would be amazing.
Basically, I need my data-source: to automatically grab the items when my vue/db request is sent so I don't have to refresh the page.
Inside my Vue app, I have the following method:
fetchApplicationsMenu() {
var self = this;
wp.apiRequest( {
path: 'fh/v1/menus/applications',
method: 'GET',
}).then(menu => self.list = menu.data);
},
which calls a GET request and then stores the data inside the return { list: {} }.
A quick fix might be to just invoke init_menu() from the component's beforeDestroy() hook, called when the dialog closes. You might choose to do it from changed() instead if the dropdown is still accessible with this dialog open.
new Vue({
// option 1:
beforeDestroy() {
init_menu('applications');
},
// option 2:
methods: {
changed() {
init_menu('applications');
}
}
})
Alternative: You already know what the final application list is in changed(), so you could update the dropdown with the new list from that method.
function update_menu(path, post_list) {
const target = document.querySelector('[data-source=' + path + ']');
// remove all existing children
Array.from(target.childNodes).forEach(x => x.remove());
post_list.forEach(post => target.appendChild(create_apps_dom_tree(post)))
}
new Vue({
methods: {
changed() {
update_menu('applications', this.$data.available);
}
}
})
I have written the following code:
<div>
<div id="app" class="container">
<div class="grid">
<article v-for="tool in tools">
<div class="title">
<h3>{{capitalizeFirstLetter(tool.name)}}</h3>
</div>
<div class="description">
{{tool.description}}
</div>
<br>
<div>
<toggle-button :value=tool.status color="#82C7EB" :width=100 :height=30 :sync="true" :labels="{checked: 'Following', unchecked: 'Unfollowing'}" #change="onChange(tool)"/>
</div>
</article>
</div>
</div>
</div>
Recently I started using Bootstrap-Vue. I'm trying to figure out how to add pagination on the bottom.
I'm not sure how aria-controls works. My goal is to have 9 blocks of tools on each page. How should I add the pagination so I could move to the next 9 blocks of tools?
Since it wasn't clear if you needed client-side pagination or serverside i made an example of both in the snippet.
For simplicity I've made it 3 per page, but you could change it to 9.
The first one gets the initial page on load, and then calls the API every time the page changes by using a watcher, that calls a method with the new page which then retrieves that place and replaces our old data with the new.
The second one loads all the data on page load, and instead slices the data array based on the per_page property, so that only the items for that page is shown.
For this I've used a computed property which automatically updates based on the properties used inside it.
Both paginations have aria-controls defined with the id of the container of our page elements. This is used to tell screen readers what elements the pagination changes.
The classes row, col-*, border, mx-auto, h2 and text-center is classes used for styling and layout and isn't part of the actual solution, so you can freely change or remove them.
new Vue({
el: '#app',
computed: {
pagination2CurrentItems() {
const startIndex = (this.pagination2.current_page - 1) * this.pagination2.per_page;
const endIndex = startIndex + this.pagination2.per_page;
return this.pagination2.items.slice(startIndex, endIndex)
}
},
created() {
this.getPagination1Data()
this.getPagination2Data()
},
filters: {
capitalizeFirstLetter(value) {
return value.charAt(0).toUpperCase() + value.slice(1)
}
},
data() {
return {
pagination1: {
items: [],
per_page: 3,
total_rows: 0,
current_page: 1
},
pagination2: {
items: [],
per_page: 3,
total_rows: 0,
current_page: 1
}
}
},
methods: {
getPagination1Data(page = 1) {
fetch(`https://reqres.in/api/unknown?page=${page}&per_page=3`)
.then((response) => {
return response.json();
})
.then((data) => {
this.pagination1.total_rows = data.total;
this.pagination1.items = data.data;
});
},
getPagination2Data() {
/*
This endpoint only has 12 items total,
so this will get all in one call
*/
fetch(`https://reqres.in/api/unknown?per_page=12`)
.then((response) => {
return response.json();
})
.then((data) => {
this.pagination2.total_rows = data.total;
this.pagination2.items = data.data;
});
}
},
watch: {
'pagination1.current_page'(newPage) {
this.getPagination1Data(newPage)
}
}
})
<script src="https://unpkg.com/vue#2.6.11/dist/vue.min.js"></script>
<script src="https://unpkg.com/bootstrap-vue#2.5.0/dist/bootstrap-vue.js"></script>
<link href="https://unpkg.com/bootstrap#4.4.1/dist/css/bootstrap.min.css" rel="stylesheet"/>
<link href="https://unpkg.com/bootstrap-vue#2.5.0/dist/bootstrap-vue.css" rel="stylesheet"/>
<div id="app" class="container">
<div id="tools_list_1" class="row">
<div class="col-12 h2 text-center">
Server pagination
</div>
<article class="col-3 mx-auto border" v-for="tool in pagination1.items">
<div class="title">
<h3>{{ tool.name | capitalizeFirstLetter }}</h3>
</div>
<div class="description">
{{ tool.pantone_value }}
</div>
</article>
</div>
<div class="row mt-2">
<div class="col-12">
<b-pagination
v-model="pagination1.current_page"
:per-page="pagination1.per_page"
:total-rows="pagination1.total_rows"
aria-controls="tools_list_1"
>
</b-pagination>
</div>
</div>
<div id="tools_list_2" class="row">
<div class="col-12 h2 text-center">
Client pagination
</div>
<article class="col-3 mx-auto border" v-for="tool in pagination2CurrentItems">
<div class="title">
<h3>{{ tool.name | capitalizeFirstLetter }}</h3>
</div>
<div class="description">
{{ tool.pantone_value }}
</div>
</article>
</div>
<div class="row mt-2">
<div class="col-12">
<b-pagination
v-model="pagination2.current_page"
:per-page="pagination2.per_page"
:total-rows="pagination2.total_rows"
aria-controls="tools_list_2"
>
</b-pagination>
</div>
</div>
</div>
If you are interested in server side pagination with url changes of query params (using vue-router in history mode) and you need browser history (back/forward) to work properly, you can check my answer to a similar question. I think it can be adapted to not use b-table.
Place pagination number in url Vuejs
I have problem when make submit chat its have error 'handlebar is not define'. I follow tutorial code in this link : https://codepen.io/drehimself/pen/KdXwxR
this is the error :
here my html code :
<div class="chat-area scrollbar-macosx scroll-wrapper">
<div class="chat-area-content scrollbar-macosx">
<ul class="container">
<!-- Label Time Chat -->
<div class="text-center mt-3">
<span class="label-time">30 Apr</span>
</div>
<!-- Merchant chat -->
<div class="d-flex justify-content-start mt-3">
<div class="chat-content-image">
<div class="upload-image">
<div class="time-image">
<span class="time-item">14:10</span>
</div>
</div>
</div>
</div>
<!-- Customer chat -->
<div class="d-flex justify-content-start mt-3">
<div class="chat-context">
<div class="chat-text">
<p></p>
</div>
<div class="chat-time">
<p>14:15</p>
</div>
</div>
</div>
<!-- Customer Chat -->
<div class="d-flex justify-content-end mt-3 mb-4">
<div class="chat-context">
<div class="chat-text">
<p></p>
</div>
<div class="chat-time">
<p>15:00</p>
</div>
</div>
</div>
</ul>
</div>
<form class="keyboard-chat">
<div class="chat-input">
<div class="attach-button mr-3 mb-3">
<button type="button" class="circle-button">
<i class="fa fa-plus"></i>
</button>
</div>
<div class="chat-input-textarea" style="padding-left: 0px;">
<div>
<textarea id="message-to-send" name="message-to-send" placeholder="Type here..." rows="3" class="keyboards f-size-12" style="max-height: 130px;"></textarea>
</div>
</div>
<div class="btn-submit-message mb-3"></div>
</div>
</form>
</div>
<script id="message-template" type="text/x-handlebars-template">
<div class="d-flex justify-content-end mt-3">
<div class="chat-context">
<div class="chat-text">
<p>{{messageOutput}}</p>
</div>
<div class="chat-time">
<p>{{time}}</p>
</div>
</div>
</div>
</script>
And here my js :
(function(){
var chat = {
messageToSend: '',
messageResponses: [
'Why did the web developer leave the restaurant? Because of the table layout.',
'How do you comfort a JavaScript bug? You console it.',
'An SQL query enters a bar, approaches two tables and asks: "May I join you?"',
'What is the most used language in programming? Profanity.',
'What is the object-oriented way to become wealthy? Inheritance.',
'An SEO expert walks into a bar, bars, pub, tavern, public house, Irish pub, drinks, beer, alcohol'
],
init: function() {
this.cacheDOM();
this.bindEvents();
this.render();
},
cacheDOM: function() {
this.$chatHistory = $('.chat-area-content');
this.$button = $('.btn-submit-message');
this.$textarea = $('#message-to-send');
this.$chatHistoryList = this.$chatHistory.find('ul');
},
bindEvents: function() {
this.$button.on('click', this.addMessage.bind(this));
this.$textarea.on('keyup', this.addMessageEnter.bind(this));
},
render: function() {
this.scrollToBottom();
if (this.messageToSend.trim() !== '') {
var template = Handlebars.compile( $("#message-template").html());
var context = {
messageOutput: this.messageToSend,
time: this.getCurrentTime()
};
this.$chatHistoryList.append(template(context));
this.scrollToBottom();
this.$textarea.val('');
// responses
var templateResponse = Handlebars.compile( $("#message-response-template").html());
var contextResponse = {
response: this.getRandomItem(this.messageResponses),
time: this.getCurrentTime()
};
setTimeout(function() {
this.$chatHistoryList.append(templateResponse(contextResponse));
this.scrollToBottom();
}.bind(this), 1500);
}
},
addMessage: function() {
this.messageToSend = this.$textarea.val()
this.render();
},
addMessageEnter: function(event) {
// enter was pressed
if (event.keyCode === 13) {
this.addMessage();
}
},
scrollToBottom: function() {
this.$chatHistory.scrollTop(this.$chatHistory[0].scrollHeight);
},
getCurrentTime: function() {
return new Date().toLocaleTimeString().
replace(/([\d]+:[\d]{2})(:[\d]{2})(.*)/, "$1$3");
},
getRandomItem: function(arr) {
return arr[Math.floor(Math.random()*arr.length)];
}
};
chat.init();
})();
Code of .js same like in the tutorial, but i only change the name class i used.
My Expectation is when i enter / click button send, the conversation is submited to the chat area.
This code requires Handlebars JS since you call Handlebars. You can it install it by follwing the installation steps on the website.
I am trying to create a chat style form. So a user inputs their data and then uses the button within my template with the class of continue-btn.
As you can see when the continue-btn is pressed it uses the nextStep method which adds 1 to the counter data property.
Within my template I then use v-if="counter >= 1" to display the next section of the chat dialog and input field.
I am then trying to use scrollTop to automatically scroll the page to the new section with the id of #conversation__tram-1. I originally tried running this block of code just after the counter had been given a value of 1:
const container = this.$el.querySelector("#conversation__tram-" + this.counter);
container.scrollTop = container.scrollHeight;
This didn't work though because I'm guessing the #conversation__tram-1 element hadn't been added to the DOM yet.
So for the sake of testing I tried wrapping it in a timeout function:
setTimeout(function(){
const container = this.$el.querySelector("#conversation__tram-" + this.counter);
container.scrollTop = container.scrollHeight;
}, 3000);
However I am left with this error when trying this:
Uncaught TypeError: Cannot read property 'querySelector' of undefined
Here is my whole single vue file:
<template>
<div id="conversation-app">
<!-- <div v-for="item in items">
{{ item.text }}
</div> -->
<div class="conversation__track">
<div id="conversation__tram-0">
<div class="conversation__item agent">
<img src="/assets/cdn.annuityadvicecentre.dev/images/theme-f/michael-chat-agent.jpg" class="conversation__item-prof-img" alt="Michael Chat Agent" />
<div class="conversation__item-content">
<p>
Hello my name is {{ agent }}, we'll compare the whole annuity market to bring you back the best annuity rates from the top providers for you. Let's get started, what's your name?
</p>
</div>
</div>
<div class="conversation__item customer" id="title-fullname">
<div class="conversation__item-content">
<p>
Hi {{ agent }}, my name is...
</p>
<div class="row">
<div class="col-4">
<select id="title" class="field-title" name="payload[title]"><option value="mr">Mr</option><option value="mrs">Mrs</option><option value="miss">Miss</option><option value="ms">Ms</option></select>
</div>
<div class="col-8">
<input v-model="customerName" id="full_name" class="field-full_name" name="payload[full_name]" type="text">
</div>
</div>
</div>
</div>
</div>
<transition name="fade">
<div id="conversation__tram-1" v-if="counter >= 1">
<div class="conversation__item agent">
<img src="/assets/cdn.annuityadvicecentre.dev/images/theme-f/michael-chat-agent.jpg" class="conversation__item-prof-img" alt="Michael Chat Agent" />
<div class="conversation__item-content">
<p>
Thanks {{ firstName }}, nice to meet you. To process your instant quote please can I have your Pension Value?
</p>
</div>
</div>
<div class="conversation__item customer">
<div class="conversation__item-content">
<p>
Sure, my pension value is...
</p>
<input id="pension_value" class="field-pension_value" placeholder="£" pattern="\d*" name="payload[pension_value]" type="number">
<div class="error-wrap error_pension_value is-hidden" data-format="<div class="error-text">:message</div>"></div>
</div>
</div>
</div>
</transition>
<div id="conversation__buttons">
<button type="button" class="continue-btn"
v-on:click="nextStep"
>Continue <i class="fa fa-chevron-right" aria-hidden="true"></i></button>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'conversation-app',
data () {
return {
agent: 'Brick',
counter: 0,
customerName: '',
}
},
methods: {
nextStep: function() {
this.counter += 1;
setTimeout(function(){
const container = this.$el.querySelector("#conversation__tram-" + this.counter);
container.scrollTop = container.scrollHeight;
}, 3000);
},
},
computed: {
firstName() {
return this.customerName.split(' ')[0];
}
}
}
</script>
Any idea why this isn't working? Thanks.
This is a good time to use arrow functions, as they preserve the context of this.
nextStep: function() {
this.counter += 1;
setTimeout(() => {
const container = this.$el.querySelector("#conversation__tram-" + this.counter);
container.scrollTop = container.scrollHeight;
}, 3000);
Altenatively, instead of the timeout you can use Vue.nextTick which is a more technically-correct way of doing this.
nextStep: function () {
this.counter += 1
this.$nextTick(() => { ... })
I have an image gallery displayed on my page. I need to implement a modal for whenever the user clicks on the images. In the modal I need to show the full size of the selected image. Here is the problem: I have already made the modal work, but when I click on any of the gallery images, the modal shows all of them together in a single modal. I need the modal to only show the one that the user clicked on.
Please note that my webpage is based on AngularJS and PHP. I used ngModal for this modal and that I'm new to using Angular (basically I know nothing, I'm learning), so please be patient with me. Here is my code:
app.js
readApp.controller('newsGallery', function($scope) {
$scope.myData = {
modalShown: false,
}
$scope.logClose = function() {
console.log('close!');
};
$scope.toggleModal = function() {
$scope.myData.modalShown = !$scope.myData.modalShown;
};
});
HTML
<div ng-controller='newsGallery'>
<modal-dialog show='myData.modalShown' width='75%' height='80%' on-close='logClose()'>
<div ng-repeat = "i in idsBlobs" >
<img src="php/visualizar_archivo.php?id={{i.id}}">
</div>
</modal-dialog>
<div class="row" style="display:flex; flex-wrap: wrap;">
<div class = "col-md-4" ng-repeat = "i in idsBlobs" >
<div class="news-image" align="center">
<img src="php/visualizar_archivo.php?id={{i.id}}" class = "img-responsive img-rounded" ng-click='toggleModal();'>
</div>
</div>
</div>
</div>
One way to have the image that the user clicked on shown in the modal is to introduce a scope variable e.g. $scope.selectedImage. Next, in the function toggleModal(), accept an argument for the image and set that scope variable to that argument.
$scope.toggleModal = function(image) {
$scope.myData.modalShown = !$scope.myData.modalShown;
$scope.selectedImage = image;
};
Next update the call to that function in the ng-click handler:
<img src="php/visualizar_archivo.php?id={{i.id}}" ng-click='toggleModal(i);' class = "img-responsive img-rounded">
Then in the modal markup, show that selected image.
<modal-dialog show='myData.modalShown' width='75%' height='80%' on-close='logClose()'>
<img src="php/visualizar_archivo.php?id={{selectedImage.id}}">
</modal-dialog>
That way the modal will only show the image that the user clicked on, instead of all images in the list.
See a demonstration of this below.
readApp = angular.module('readApp', ["ngModal"]);
readApp.controller('newsGallery', function($scope) {
$scope.idsBlobs = [{
"id": 'MA',
"src": "http://www.animatedimages.org/data/media/96/animated-lighthouse-image-0032.gif"
},
{
"id": "MU",
"src": "http://icons.iconarchive.com/icons/aha-soft/large-home/128/Museum-icon.png"
}
];
$scope.myData = {
modalShown: false
}
$scope.logClose = function() {
console.log('close!');
};
$scope.toggleModal = function(image) {
$scope.myData.modalShown = !$scope.myData.modalShown;
$scope.selectedImage = image;
};
});
.img-thumb {
height: 48px;
width: 48px;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="//elliott.andrewz.org/cdn/ng-modal.min.js"></script>
<link href="//elliott.andrewz.org/cdn/ng-modal.css" rel="stylesheet" />
<div ng-app="readApp" ng-controller="newsGallery">
<modal-dialog show="myData.modalShown" width="75%" height="80%" on-close="logClose()">
<img src="{{ selectedImage.src }}" />
</modal-dialog>
<div class="row" style="display:flex; flex-wrap: wrap;">
<div class="col-md-4" ng-repeat="i in idsBlobs">
<div class="news-image" align="center">
<img src="{{ i.src }}" class="img-responsive img-rounded img-thumb" ng-click="toggleModal(i);" />
</div>
</div>
</div>
</div>