Ember Shepherd doesn't recognize DOM changes - javascript

Description
I am using ember with ember-shepherd and have come across an annoying issue. Every time the DOM is changed during the tutorial, shepherd doesn't seem to recognize any of the changes and therefore breaks when trying to anchor to a tag that wasn't there when it originally loaded.
Does anyone know how to make shepherd reload the DOM without reloading the entire page?
Example
controller.js
import Ember from 'ember';
export default Ember.Controller.extend({
tour: Ember.inject.service(),
showDiv: false,
setupTutorial: function () {
this.set('tour.steps', [
{
id: '1',
options: {
attachTo: '#some-div top',
text: 'Read me!',
when: {
hide: () => {
this.set('showDiv', true);
}
}
}
},
{
id: '2',
options: {
attachTo: '#hidden-div top',
text: 'Shhh I am hidden.'
}
}
]);
this.get('tour').trigger('start');
}.on('init')
});
template.hbs
<div id="some-div">
More stuff.
</div>
{{#if showDiv}}
<div id="hidden-div">
Blargh
</div>
{{/if}}
The first tour step will show perfectly fine, but the second step will not locate the "hidden-div" anchor after it is inserted into the DOM.

In current version of ember-shepard addon all tour dom elements of steps should exists before start of tour .trigger('start');

Related

How can I let the list page remember some params?

I have a Vue project, but there is a issue to me,
In my router.js:
{
path: '/home/aftersale_physicalserver_workpanel/:use_flag/:areapartition_homeshowtext',
meta: { keepAlive: true },
icon: 'compose',
name: 'aftersale_physicalserver_workpanel',
title: 'work panel',
component: resolve => {
require(['./views/main_home/home/components/general_admin_staff_panel/physicalserver/physicalserver_workpanel.vue'], resolve);
}
},
there is the code:
...
<template>
<lml-page
ref="lml_page_ref"
v-if=" origin_data && origin_data.count"
:data_count="origin_data.count"
:current.sync="cur_page"
#change_page_for_parent="server_change_page">
</lml-page>
</template>
...
<script>
export default {
props: {
...
cur_page: 1,
},
</script>
you see the cur_page is the page number. I want the URL append the page number.
because when I enter a detail page, when I go back, there will go to page 1 by default.
My purpose
my purpose is let the list_page remember some params. such as the upper page_number, and some search params. but in Vue I don't know how.
When I from a searched params page enter a detail page, when I go back:
<span class="go-left" #click="$router.go(-1)">
<Icon type="chevron-left"></Icon>
<span>Go back</span>
</span>
there will get a list_page without the searched data.
You would need to use either local storage or a central state, what I use is vuex like this:
onPageChange: function (pageNo) {
this.$store.dispatch(this.$mts.some.SOMETHING, pageNo);
},
Then you can call your store wherever you need and get the page number.
Look for the vuex docs on how to setup state management.
Like this:
this.page = this.$store.getters.some.page

error on removing an object in list in vue

I'm deploying a client app with vue.js.
In this app I have some tabs that are being rendered using v-for. this tab array is formated like this in vuex store:
tabs: [
{
id: 1,
title: 'first tab',
number: '09389826331',
accountName: 'DelirCo',
contactName: 'PourTabarestani',
startTime: '2019-02-25 15:11:30',
endTime: '2019-02-25 18:04:10',
duration: null,
subject: '',
description: ''
},
{
id: 2,
title: 'second tab',
number: '09124578986',
accountName: 'Cisco',
accountGUID: '',
contactName: 'Arcada',
contactGUID: '',
startTime: '2019-02-25 15:11:45',
endTime: '2019-02-25 15:13:55',
duration: null,
subject: '',
description: ''
}
]
I'm using getters to load the tabs in my vuex store which renders the tabs using following template:
<template>
<div id="Tabs">
<vs-tabs color="#17a2b8" v-model="selectedTab">
<vs-tab v-for="tab in tabs" :key="tab.id" :vs-
label="tab.title">
<Tab :tab="tab"></Tab>
</vs-tab>
</vs-tabs>
</div>
</template>
I'm using vuesax components for creating the tabs displays.
each object in this list is a tab in my front end which shows the related data when I click each tab.
it's doing perfectly fine when I try to show the tabs or even adding another object in the array.
the problem is when I try to remove a certain item from this array the content goes away but the tab title (the button where I can select the tab with it) remains on the page.
I'm using
state.tabs.splice(objectIndex, 1)
state.selectedTab -= 1
for removing the tab and changing the selected tab to the previous one.
but as I said the title of the tab is not being removed like the picture below:
and when I click on that tab I'm getting this error:
webpack-internal:///./node_modules/vuesax/dist/vuesax.common.js:4408 Uncaught TypeError: Cannot set property 'invert' of undefined
at VueComponent.activeChild (webpack-internal:///./node_modules/vuesax/dist/vuesax.common.js:4408)
at click (webpack-internal:///./node_modules/vuesax/dist/vuesax.common.js:4127)
at invoker (webpack-internal:///./node_modules/vue/dist/vue.esm.js:2140)
at HTMLButtonElement.fn._withTask.fn._withTask (webpack-internal:///./node_modules/vue/dist/vue.esm.js:1925)
anyone have any suggestion around this matter ?
You can just add a key to the parent element to force the rerender.
<div :key="forceRender">
<vs-tabs :value="activeTab">
<template v-for="item in items">
<vs-tab :label="item.name" #click="onTabChange(item)">
<div class="con-tab-ejemplo">
<slot name="content"></slot>
</div>
</vs-tab>
</template>
</vs-tabs>
</div>
After that add a watcher on the items:
watch: {
items (val) {
this.forceRender += 1
}
},
It seems to be an issue with the vuesax library. The <vs-tabs> component doesn't support <vs-tab> components being added/removed as child components dynamically.
Here's an excerpt from the vsTab.vue file in the repo:
mounted(){
this.id = this.$parent.children.length
this.$parent.children.push({
label: this.vsLabel,
icon: this.vsIcon,
id: this.$parent.children.length,
listeners: this.$listeners,
attrs: this.$attrs
})
}
When the <vs-tab> component is mounted, it adds itself to the parent (<vs-tabs>) as a child, but it does not remove itself from this array when it is destroyed.
You can open an issue on their GitHub page to see if they could support what you want, or you can submit a pull request.

Enyojs Routing and rendering issue

I am new to Enyo and trying to do mobile web application with router and multiple pages, it is not actually a single page application but we want to maintain different header and footer and content in different pages, so we tried with multiple enyo application.
It is working as expected but the issue is i can see multiple times of rendering the page where its configured in the router. I am not able to find out. I am using enyo 2.5.1.1.
Here is my app.js.
enyo.kind({
name: "myapp.Application",
kind: "enyo.Application",
view: "myapp.MainView",
components :[
{
name: 'router',
kind: 'enyo.Router',
routes: [
{path: 'next', handler: 'nextPage'}
],
publish: true
}
],
nextPage : function(){
// new myapp.MainView1().renderInto(document.body);
new myapp.Application1();
}
});
enyo.kind({
name: "myapp.Application1",
kind: "enyo.Application",
view: "myapp.MainView1",
});
enyo.ready(function () {
new myapp.Application({name: "app"});
});
view.js
enyo.kind({
name: "myapp.MainView",
kind: "FittableRows",
fit: true,
components:[
{kind: "onyx.Toolbar", content: "Hello World"},
{kind: "enyo.Scroller", fit: true, components: [
{name: "main", classes: "nice-padding", allowHtml: true}
]},
{kind: "onyx.Toolbar", components: [
{kind: "onyx.Button", content: "Tap me", ontap: "helloWorldTap"}
]}
],
create : function(){
this.inherited(arguments);
console.log("MainView is created in memory");
},
rendered : function(){
this.inherited(arguments);
console.log("MainView is created in rendered into DOM");
},
helloWorldTap: function(inSender, inEvent) {
//this.$.main.addContent("The button was tapped.
");
//window.location="#login";
new myapp.Application().router.trigger({location:'next',change:true});
}
});
view1.js
enyo.kind({
name: "myapp.MainView1",
kind: "FittableRows",
fit: true,
components:[
{kind: "onyx.Toolbar", content: "Hai-->>"},
{kind: "enyo.Scroller", fit: true, components: [
{name: "main", classes: "nice-padding", allowHtml: true}
]},
{kind: "onyx.Toolbar", components: [
{kind: "onyx.Button", content: "Go Back", ontap: "helloWorldTap"}
]}
],
create : function(){
this.inherited(arguments);
console.log("MainView1 is created in memory");
},
rendered : function(){
this.inherited(arguments);
console.log("MainView1 is created in rendered into DOM");
},
helloWorldTap: function(inSender, inEvent) {
//this.$.main.addContent("The button was tapped.
");
//window.location="#";
new myapp.Application().router.trigger({location:'/ ',change:true});
}
});
here whenever i click the "Tap me" in the Mainview , it will load the MainView1. but i can see multiple time the Mainview1 is rendering ,it keeps incrementing 3 times every tap.
It's probably because you're not returning true from the helloWorldTap() handler. That will cause the tap event to continue up the ownership hierarchy and any other components that happen to hear it will run their handlers, as well.
Try just adding return true; at the end and see if it helps.
I might suggest implementing your changing header/footer content based on "pages" to something that better suits a single page app, but if you have multiple apps already running, it might not be worth it.
This is certainly a pathological use case. I don't think it was intended that you would create multiple Application objects that would render into the same document that way (you can render them into separate DOM nodes, but I wouldn't expect you to be able to render two applications into document.body.)
Further, in your tap handler, you're creating another application and then calling its router, which will cause yet another application to be created. It doesn't make sense to create a new Application to call its router. You can just use this.app.router... to access your current App's router.

Is it possible to change properties in Ember routes?

What is the correct way to set properties on an Ember route? I have a title message that I would like to be displayed on page load and then I would like to change that title as the user interacts with the page.
import Ember from 'ember';
export default Ember.Route.extend({
title: "Welcome!",
model(thing1) {
return Ember.RSVP.hash({
thing1: this.store.findRecord('thing1', thing1.thing_id),
thing2: this.store.findAll('thing2'),
thing3: this.store.findAll('thing3')
});
},
actions: {
changeTitle() {
this.set("title", "I changed!")
}
}
});
In my template I load another component and pass in the value for title
{{title-tile title=title}}
And in my component title, I print out (theoretically) the title:
{{title}}
I have also tried removing the intermediary step and just printing out the title directly but that doesn't work.
In the console I have no errors, and I am otherwise able to print out the model data from the RSVP hash. There is no (obvious) documentation on this. All documentation focuses on printing out model properties.
What am I missing?
Edit
It appears to me that routes are just meant to handle models and that components are supposed to take care of everything else.
I've actually tried explicitly calling the title to be set on route load and it still doesn't work.
...
init() {
title: "Welcome!"
}
...
You could use a computed property in hash passed to template:
export default Ember.Route.extend({
title: 'Welcome!',
model(thing1) {
return Ember.RSVP.hash({
// ... omitted for brevity
title: Ember.computed(() => this.get('title')) // computed property
});
},
actions: {
changeTitle() {
this.set("title", "I changed!")
this.refresh(); // it is required to refresh model
}
}
});
Working demo.
Full code behind demo.

Backbone.js router/views logic

I am writing my first Backbone.js application and I am having some trouble figuring out the best way to program it. I have 2 main views:
Shows an index of all my models.
Shows a specific model for editing.
But #2 has many different 'modules' like I can edit the 'news' section, or 'about' section etc...
All these modules are in a navigation bar.
That navigation bar is hidden when I am displaying view # 1 (index of all models). It is visible in view # 2(a specific model) in order to navigate between different modules.
I have routes setup like this:
routes: {
'', 'index',
'communities': 'index',
'communities/:id': 'main',
'communities/:id/news', 'news',
'communities/:id/about', 'about'
},
So my question is, when 'news' or 'about' action is called, do I add a navigation bar in each method? Isn't that redundant? I am going to have like 8-10 different modules, add navigation bar each time seems very repetitive. Is there a better way?
The only time I want the navigation bar to be hidden is when showing index.
I came across this same problem when I created my first somewhat complex Backbone app. Along with your concern of redundant code, I was concerned about events bound to my navbar that may not get unbound as the navigation bar changed. To solve the problem, I wound up creating a view hierarchy, with one manager view managing the navigation bar a whole, and separate views for each type of navigation menu I wanted to display, which would be passed to the manager view to render to the page.
Here's an example of my implementation.
Before we start, here is a close function I added to Backbone's View prototype which unbinds events and removes the view
Backbone.View.prototype.close = function() {
if(this.beforeClose) { this.beforeClose(); }
this.remove();
this.unbind();
}
First, here is my Manager View. Its render function closes whatever menu is currently displayed and replaces it with the one passed to it as view. While slightly redundant, I created an explicit empty function to make my router code easier to understand.
var App.Views.SubNavBar = Backbone.View.extend({
currentView: null,
el: '#subnav-wrap',
render: function(view) {
if(this.currentView) { this.currentView.close(); }
this.currentView = view;
this.$el.html(view.el);
},
empty: function() {
if(this.currentView) { this.currentView.close(); }
this.currentView = null;
}
});
Second, here is a base view that all of my specific navigation menu views extend. Since they will all have the same tagName, className, id, and initialize and render functions, this keeps repetition to a minimum
var App.Views.SubNavBase = Backbone.View.extend({
tagName: 'ul',
className: 'nav nav-pills',
id: 'subnav',
template: _.template($('#tmpl-subnav').html(),
initialize: function() {
if(this.setLinks) { this.setLinks(); }
this.render();
},
render: function() {
this.$el.html(this.template({links:this.links}));
return this;
}
});
Here is an example of a view for a specific navigation menu. You can see that all I need to do is define the links I want to appear in the menu. When I instantiate this view, the functions of SubNavBase will handle populating the view with the required HTML. Note that I also have some events attached to this view.
var App.Views.Projects.DisplayNav = App.Views.SubNavBase.extend({
setLinks: function() {
this.links = {
'Edit Project': {
icon: 'edit',
class: 'menu-edit',
href: '#projects/'+this.model.get('id')+'/edit'
},
'Add Group': {
icon: 'plus',
class: 'menu-add-group',
href: '#projects/'+this.model.get('id')+'/groups/new'
},
'Delete Project': {
icon: 'trash',
class: 'menu-delete',
href: '#'
}
}
},
events: {
'click a.menu-delete' : 'delete'
},
delete: function(e) {
e.preventDefault();
// here goes my code to delete a project model
}
});
Now, here is the underscore.js template I use to turn the links object above into a list of <li> elements. Note that I use <# instead of <% for my templates since this is a rails app and rails already uses <%
<script type="text/template" id="tmpl-subnav">
<# _.each(links,function(link, title) { #>
<li>
<a href="<#= link.href #>" class="<#= link.class #>">
<i class="icon-<#= link.icon #>"></i>
<#= link.title #>
</a>
</li>
<# }); #>
</script>
Finally, to put it all together, here is an example Router function that creates and renders the nav menu. The steps that occur are as follows:
App.Views.Projects.DisplayNav gets passed a model and populates its this.el with the corresponding HTML, as determined by the underscore.js template
App.SubNavBar has its render function called with the new menu view
App.SubNavBar checks to see if there is currently another menu in the navigation bar; if so, it calls its view's close() function
App.SubNavBar finally appends the passed view's HTML to itself, maintaining a reference to the view for later use
I've included only the relevant parts of the router code
var App.Routers.Projects = Backbone.Router.extend({
routes: {
'projects/:id' : 'display'
},
display: function(id) {
var p = projects.get(id);
var subnav = new App.Views.Projects.DisplayNav({model:p})
App.SubNavManager.render(subnav); // App.SubNavManager is an instance of App.Views.SubNavBar
}
});
The benefit to all of this is that I can now attach events to my menu-specific views, and the manager view will take care of unbinding them if the user navigates to different content and the menu changes.
Of course, there are many other patterns you can use to handle navigation menus, but hopefully this will help you on the path.
Try this:
routes: {
'', 'index',
'communities': 'index',
'communities/:id': 'main',
'communities/:id/:section': 'openSection'
},
openSection : function(id, section){
if( section ){
this.addNavigationBar();
}
switch( section ){
case 'news' :
this.news();
break;
case 'about' :
this.about();
break;
default:
this.main();
}
}
If your url contents a section you will add the navigation bar and then call you normal method as you have.

Categories

Resources