I want to implement a vuejs component inside an existing bootstrap slim modal.
In the modal I reference the container of the component like usually:
form.slim
= modal do
= javascript_pack_tag 'product_asset', 'data-turbolinks-track': 'reload'
#product_assets[
data-product-asset-routes=JsRoutes.generate(include: /product_asset/)
]
end
Outside of a modal it works properly. But in this action it doesn't.
The console output shows this:
Source map error: Error: request failed with status 404
Resource URL: null
Source Map URL: product_asset-ed5ee8937047520ba766.js.map
Anyone of you handled with this kind of problems?
I expected an event to fire. Something like turbolinks:load. But this is an XHR-Request where this kind of stuff doesnt happen.
From:
document.addEventListener(turbolinks:load => ({
const productAsset = new Vue({
el: '#product_asset_form',
store,
railsI18n,
productAssetRoutes,
render: h => h(ProductAsset, { props: { ...root_element.dataset } }),
}).$mount()
)}
To:
const productAsset = new Vue({
el: '#product_asset_form',
store,
// this is vue-i18n magic...
i18n: railsI18n,
productAssetRoutes,
render: h => h(ProductAsset, { props: { ...root_element.dataset } }),
}).$mount()
And now it fires immediatly
Related
I created a new project for a shopify app with rails 7 and shakapacker. I want to use Vue components in my .slim files. The problem is that Vue doesn't seem to be loaded in my app, although I don't get any errors.
Here is what I did:
// config/webpack/rules/vue.js
const { VueLoaderPlugin } = require('vue-loader')
module.exports = {
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader'
}
]
},
plugins: [
new VueLoaderPlugin()
],
resolve: {
extensions: [
'.vue'
]
}
}
// config/webpack/webpack.config.js
const { webpackConfig, merge } = require('shakapacker')
const vueConfig = require('./rules/vue')
module.exports = merge(vueConfig, webpackConfig)
// app/javascript/packs/application.js
import HelloWorld from '../components/HelloWorld'
import { createApp } from 'vue'
const app = createApp({
el: '#app'
})
app.component('helloworld', HelloWorld)
document.addEventListener('DOMContentLoaded', () => {
app
})
// app/javascript/components/HelloWorld.vue
<template>
<h1>Hello world</h1>
</template>
<script>
export default {
name: 'HelloWorld'
}
</script>
/ app/views/layouts/embedded_app.slim
doctype html
html[lang="en"]
head
meta[charset="utf-8"]
- application_name = ShopifyApp.configuration.application_name
title
= application_name
= stylesheet_link_tag "application", "data-turbo-track": "reload"
= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload'
= csrf_meta_tags
body
#app
.wrapper
main[role="main"]
= yield
= content_tag(:div, nil, id: 'shopify-app-init', data: { api_key: ShopifyApp.configuration.api_key,
shop_origin: #shop_origin || (#current_shopify_session.shop if #current_shopify_session),
host: #host,
debug: Rails.env.development? })
And finally, the view where I just want to display the HelloWorld.vue component:
/ app/views/home/index.slim
helloworld
However, nothing is displayed and I have no errors. I tried to modify the creation of the app in this way, to see if the log appears:
// app/javascript/packs/application.js
import HelloWorld from '../components/HelloWorld'
import { createApp } from 'vue'
const app = createApp({
el: '#app',
created () {
console.log('ok')
}
})
app.component('helloworld', HelloWorld)
document.addEventListener('DOMContentLoaded', () => {
app
})
but then again, I have nothing in console, so I'm not even sure that the app is well rendered. On the other hand, I checked that the DOMContentLoaded event is indeed triggered and it is.
I'm not very comfortable with webpack so I don't know if something is wrong with my configuration, I followed shakapacker's README.
I don't think this is related, but the app is rendered in a Shopify test store via an Ngrok tunnel.
I don't know where to look anymore... Does anyone have an idea?
Thanks in advance
I haven't written any VueJS in a long time, but this is usually what I do in my application.js using React & Shopify Polaris components.
function initialize() {
const rootElement = document.getElementById('app')
const root = createRoot(rootElement);
/* some app bridge code I removed here */
/* react 18 */
root.render(
<BrowserRouter>
/* ... */
</BrowserRouter>
)
}
document.readyState !== 'loading' ? initialize() : document.addEventListener('DOMContentLoaded', () => initialize())
If your <div id="app"> is EMPTY when inspected with browser tools, my first guess would be you're creating an instance, but not actually rendering it in the end.
An application instance won't render anything until its .mount() method is called.
https://vuejs.org/guide/essentials/application.html#mounting-the-app
I would've commented to ask first, but I don't have enough reputation points to do so
I create a view component to handle some selects...
I need to load this component on a single Rails View.
I init my component with:
import Vue from 'vue'
import Product from '../components/product.vue'
import axios from 'axios';
Vue.prototype.$http = axios
document.addEventListener('DOMContentLoaded', () => {
document.body.appendChild(document.createElement('app'))
console.log('caricato Vue');
const app = new Vue({
render: h => h(Product)
}).$mount('#product_search')
})
And in my Rails page I have the #product_search div
Rails try to load the component on every page and give me the error:
vue.runtime.esm.js:619 [Vue warn]: Cannot find element: #product_search
Why?
Because Vue tries to render the component (on #product_search) when document is ready, meaning on every page of your application.
You can add a condition to prevent the null error:
document.addEventListener('DOMContentLoaded', () => {
let element = document.querySelector('#product_search')
if (element) {
document.body.appendChild(document.createElement('app'))
console.log('caricato Vue');
const app = new Vue({
render: h => h(Product)
}).$mount('#product_search')
}
})
I'm getting a WARNING: Tried to load angular more than once. when trying to use nested views on a Ruby on Rails and Angular app. I'm using ui-router and angular-rails-templates.
Angular config and controllers:
var app = angular.module('app', ['ui.router', 'templates']);
app.factory('categories', ['$http', function($http) {
var o = {
categories: []
};
o.get = function (id) {
return $http.get('/categories/' + id + '.json').then(function (res) {
return res.data;
});
};
return o;
}]);
app.config([
'$stateProvider',
'$urlRouterProvider',
function($stateProvider, $urlRouterProvider) {
$stateProvider
.state('home', {
url: '/home',
templateUrl: 'home/_home.html',
controller: 'MainCtrl',
});
$stateProvider
.state('home.categories', {
url: '/categories/:categoryId',
templateUrl: 'categories/_categories.html',
controller: 'CategoriesCtrl',
resolve: {
category: ['$stateParams', 'categories', function($stateParams, categories) {
return categories.get($stateParams.categoryId);
}]
}
});
$urlRouterProvider.otherwise('/home');
}]);
app.controller('MainCtrl', function ($state) {
$state.transitionTo('home.categories');
});
app.controller('CategoriesCtrl', [
'$scope',
'categories',
'category',
function($scope, categories, category) {
$scope.posts = category.posts;
}
]);
And the templates:
_home.html (*edited: deleted body and ng-app from this template, following #apneadiving advice, and used the right path, as pointed by #Pankaj Now, views are rendered properly, but the 'loading more than once' persists)
Category One
Category Two
<ui-view></ui-view>
_categories.html
<ul ng-repeat="post in posts">
<li>{{post.title}}</li>
</ul>
app/views/layouts/application.html.erb
<!--<rails head>-->
<body ng-app="app">
<ui-view></ui-view>
</body>
What is happening is that the state 'home' appears and when I click on the links, the URL changes but nothing else happens. And I get the message that Angular is being loaded more than once.
EDITED: Got it working. It was a <%= yield %> conflict in another part of the app that should have nothing to do with the Angular part.
As you are defined the child states, you need to change your URL's to /home as they are child of home state, the URL gets inherited from parent state.
Category One
Category Two
You shouldnt boot your app in a template like you do.
Add the ng-app to your html's body and remove it from the template (actually your template shouldnt bear the body either)
I've read many tutorials and made a search on the .net... but still I'm in trouble with Backbone.js. This is my simple scenario:
A Rails application responds to a GET request with a JSON collection of objects.
I want to dynamically build a list of table-rows with Backbone collections, when DOM is ready. This is the code is confusing me:
HTML part:
<script type="text/template" id="tmplt-Page">
<td>{{=title}}</td>
<td>{{=description}}</td>
</script>
Backbone's script:
$(function(){
var Page = Backbone.Model.extend({});
var Pages = Backbone.Collection.extend({
model: Page,
url: '/pages'
});
var pages = new Pages([
{title: 'ProvA1', description: ''},
{title: 'ProvA2', description: ''}
]);
var PageView = Backbone.View.extend({
tagName: 'tr',
template: _.template($('#tmplt-Page').html()),
render: function() {
this.$el.append(this.template(this.model.toJSON()));
return this;
}
});
var AppView = Backbone.View.extend({
el: $("#results"),
initialize: function () {
_.bindAll(this, 'render');
pages.on('reset', this.render)
},
render: function() {
this.$el.empty();
pages.each( function( page ) {
var view = new PageView({
model : page
});
this.$el.append(view.render().el);
});
return this;
}
});
var appview = new AppView;
});
Nothing renders on the screen.
There seem to be 2 problems:
1) fetch() is asynchronous, so the code is executed before the end of the ajax round-trip.
2) If I manually load some objects into the collection, this piece of code "this.template(this.model.toJSON())" does not substitute jSON attributes
EDIT :
To use mustache tags I wrote this code before all:
First, as you said, fetch() is asynchronous, but it triggers the 'reset' event when it completes, so you should add this in AppView.initialize:
pages.on('reset', this.render)
Second, you never insert the HTML of PageView anywhere. Add this in AppView.render:
// at the beginning
var self = this;
// and in the forEach loop
self.$el.append(view.el);
Third, at the beginning of AppView.render, you should clear the content of this.$el.
EDIT:
You still had a couple issues:
You are using underscore templates with mustache tags ({{ }} -> <%= %>)
Missing var self = this in render
You are not calling appview.render() ! :)
Here's your code working on jsfiddle: http://jsfiddle.net/PkuqS/
I have a Rails 3.2.3 app with Backbone.js and I'm using pushState on my Backbone.history.
The Problem
When I click on a link which goes to say '/foo' to show appointment with ID: 1, then Backbone router gets to that first, which I can quickly see before Rails router takes over and complains that there is no route for /foo.
My Backbone.js code
Here is my backbone router.
window.AppointmentApp = new (Backbone.Router.extend({
routes: {
"": "index",
"foo": "foo",
"appointments": "index",
"appointments/:id": "show"
},
foo: function(){
$("#app").append("foo<br />");
},
initialize: function() {
this.appointments = new Appointments();
this.appointmentListView = new AppointmentListView({ collection: this.appointments });
this.appointmentListView.render();
},
start: function() {
Backbone.history.start({pushState: true});
},
index: function() {
$("#app").html(this.appointmentListView.el);
this.appointments.fetch();
},
show: function(id) {
console.log("Enter show");
}
}));
It should stay on the same page and attach a 'foo' to the end of the #app div, but it never does.
Backbone index viewer
window.AppointmentListView = Backbone.View.extend({
template: JST["appointments/index"],
events: {
"click .foo": function(){Backbone.history.navigate("foo");},
},
comparator: function(appointment){
return appointment.get('topic');
},
initialize: function(){
this.collection.on('reset', this.addAll, this);
},
render: function(){
this.$el.html(this.template);
this.addAll();
return this;
},
addAll: function() {
this.collection.forEach(this.addOne, this);
},
addOne: function(appointment){
var appointmentView = new AppointmentView({model: appointment});
this.$el.append(appointmentView.render().el);
}
});
app/assets/templates/appointments/Index.jst.ejs
<h1>Appointments</h1>
Say Foo
<a href=appointments/add>Add</a>
<div id="app"></div>
I was using pushState as it allows me to keep a history and the Back button functionality.
The Backbone.history.navigate doesn't call my Backbone route, it calls the Rails route instead. How do I go about fixing this?
Should I be trying to setup Backbone to accept routes such as 'appointments/1' and taking control or do I have to use a click event with a Backbone.history.navigate call like above?
You need to return false from your click .foo event handler, otherwise the browser will continue as if you'd clicked the link normally and request the actual /foo page from the server.
I think you've also got the call to Backbone.history.navigate("foo"); wrong - Backbone.history doesn't have a navigate function as far as I can see from the documentation. You should actually be calling .navigate on your Backbone.Router instance, and passing in the trigger option to cause it to call trigger the route. For example:
window.AppointmentApp.navigate("foo", { trigger : true } );
You may already know this but if you're planning on using pushState then you should really update your server side to support all the URLs that your client side does. Otherwise if a user decides to copy & paste the URL into another tab, they will just run into rails complaining that there is no route.