i would like to navigate my nested view in backbone app with routes. I have next code:
var StoreRouter = Backbone.Marionette.AppRouter.extend({
appRoutes: {
'item/:name/' : 'showItem',
"item/:name/species/:speciesName/" : "showSpecies"
}
});
var StoreCtrl = Marionette.Object.extend({
showItem: function(name){
console.log("showItem");
/* execute function for show Item */
},
showSpecies: function(name, speciesName){
console.log("showSpecies");
/* execute function for show Species inside Item Layout */
}
});
So, i need to show species when route is "item/:name/species/:speciesName/" but i get only triggering showSpecies fucntion, not both. What shall i do to trigger showItem and then showSpecies functions when route is "item/:name/species/:speciesName/" ?
There is nothing brand new here. Simply call your showItem function directly from showSpecies.
Additionally, you could use routes hash instead of appRoutes and then it is possible to do so:
var StoreRouter = Backbone.Marionette.AppRouter.extend({
routes: {
'item/:name/' : 'showItem',
'item/:name/species/:speciesName/' : function(){
this.showItem();
this.showSpecies();
}
}
});
Related
How can i pass a Javascript Variable to a Vue Component?
I have this jQuery function which generates the menu and pushes all the Values inside the array menu:
var menu = [];
$(document).ready(function(){
$('.service-desc-wrap h2,.cta-button').each(function(){
if($(this).hasClass('cta-button')) {
if($(this).attr('title') && $(this).attr('href')) {
var linkTitle = $(this).attr('title');
var href = $(this).attr('href');
data = [
title = linkTitle,
href = href,
]
menu.push(data);
$('#menu, #menuMobile').append('<a class="menuText" href="'+href+'">'+linkTitle+'</a>')
};
} else {
var tag = $.slugify($(this).text());
$(this).attr('id',tag);
var linkTitle = $(this).text();
if($(this).attr('title')) {
var linkTitle = $(this).attr('title');
};
data = [
title = linkTitle,
href = tag,
]
menu.push(data);
$('#menu, #menuMobile').append('<a class="menuText" href="#'+tag+'">'+linkTitle+'</a>')
}
});
});
I want to pass the array to a Vue Component called
<service-menu-component></service-menu-component>
The jQuery Function and the Component are inside a blade.php file, i'm using Laravel as a backend.
Any Vue component has access to the global scope (a.k.a window object), in which $ performs. You don't have to do anything special about it. In simpler words, if a variable has been declared in global scope at the time your Vue component is created - Vue can access it. But Vue won't react to later mutations performed on the contents of that variable. Not out of the box, anyway.
In Vue, that behavior is called reactivity. If that's what you want, you could use Vue.observable():
declare a const, holding a reactive reference (store.menu in this example - name it to whatever makes sense to you)
use a computed in your Vue component, returning the reactive reference
at any point, (before or after Vue instance's creation) modify the reference from anywhere (including outside Vue component/instance) and the Vue instance will get the change
Proof of concept:
Vue.config.productionTip = false;
Vue.config.devtools = false;
// you don't need the above config, it just suppresses some warnings on SO
// declare store, with whatever contents you want:
const store = Vue.observable({menu: []});
// optionally push something to menu:
// works before Vue instance was created
store.menu.push({foo: 'bar'});
$(function() {
// optionally push something to menu
// also works after Vue instance was created - i.e: 3s after $(document).ready()
setTimeout(function() {
store.menu.push({boo: 'far'});
}, 3000)
})
new Vue({
el: '#app',
computed: {
menu() {
// this injects the store's menu (the reactive property) in your component
return store.menu
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.12/vue.js"></script>
<div id="app">
<pre v-text="menu"></pre>
</div>
The computed doesn't have to be on the root Vue element (it can be inside your <service-menu-component> component). The above is just a basic implementation, to demo the principle.
Use props:
<service-menu-component :data=YOUR_ARRAY></service-menu-component>
In the component:
props: ['data'] // the data being passed.
Yes, It's possible you need to import this jQuery snippet file to your parent component, and then pass it down to your service-menu-component.
Here's how the code should look like:
Your Parent.vue
<template>
<service-menu-component :data=menu></service-menu-component>
</template>
<script src="<pathToYourJqueryFile>"></script>
And then in your service-menu-component:
<template>
<div>
{{ menu }}
</div>
</template>
<script>
export default {
name: 'service-menu-component',
props: {
menu: {
type: Array
}
}
}
</script>
What made it work and seemed simple after trying different things is moving the jQuery function inside the component mounted hook like this:
mounted() {
var menu = [];
$(document).ready(function(){
$('.service-desc-wrap h2,.cta-button').each(function(){
if($(this).hasClass('cta-button')) {
if($(this).attr('title') && $(this).attr('href')) {
var linkTitle = $(this).attr('title');
var href = $(this).attr('href');
data = {
'title': linkTitle,
'href': href,
}
menu.push(data);
$('#menu, #menuMobile').append('<a class="menuText ml-2" href="'+href+'">'+linkTitle+'</a>')
};
} else {
var tag = $.slugify($(this).text());
$(this).attr('id',tag);
var linkTitle = $(this).text();
if($(this).attr('title')) {
var linkTitle = $(this).attr('title');
};
var data = {
'title': linkTitle,
'href': tag,
}
menu.push(data);
$('#menu, #menuMobile').append('<a class="menuText ml-2" href="#'+tag+'">'+linkTitle+'</a>')
}
});
console.log(menu);
});
this.menu = menu;
},
It worked like a charm.
#Şivam Kuvar verdiği örnekteki gibi :data='menu' yazan yeri :data="json_encode($controllerdata)" ile değiştir ve verilerini kullanmaya hazırsın. veri yapına göre #{{ data.data[0].title }} olabilir örneğin veya #{{ data.title }} bu gelen veriye bağlı dostum.
Just like #Şivam Kuvar's example, replace :data='menu' with :data="json_encode($controllerdata)" and you're ready to use your data. According to the data structure, it can be #{{ data.data[0].title }} for example or #{{ data.title }} it depends on the incoming data, my friend.
I'm building an AngularJS app, and basically I have a menu on top of my page then when I select an item is supposed to slide down some content of my page and show the menu in that area. I've read something about creating a directive but isn't still clear to me how can I do it. I show an example of my page:
Then when I select an item from the Menu, the static image disapears and gives place to a "sub menu" and the sub menu content itself.
In some cases, where the "SubMenu" + "Content" height is bigger then the height of the image, the rest of the content will slide down.
My main concern is how to show this SubMenu depending on the menu selected (in the black square). My idea was to create some menu template (with all the html and css created) and then just bind the different content to this template and show it on the div that I want (in this case the div that started with the static image). But since AngularJS is new to me I'm having some problems to put it in pratice.
You can see plunker example here:
https://plnkr.co/edit/xJoF4IuDtJlGzWmTYoqZ?p=preview
And here is the angular code explanation:
angular.module('plunker', []);
function MainCtrl($scope, menuSvc) {
// main controller sets the menu contents to the service
menuSvc.putAll([{t:"item1"},{t:"item2"}]);
}
// a very simple menu service that keeps an object of menu items
angular.module('plunker').factory("menuSvc", [ function( ) {
var items;
var clear = function(){
items = {};
};
var getAll = function(){
return Object.keys(items);
};
var put = function( item ){
items[item.t] = item;
};
var putAll = function( itemArray, dontClean ){
if( !dontClean ){
clear();
}
itemArray.forEach(
function(i){
put(i);
}
);
};
clear();
return {
put: put,
getAll: getAll,
putAll: putAll,
clear: clear
};
}]);
// directive that gets its content from the service
angular.module('plunker').directive('menu', function(){
return {
restrict: 'E',
scope: {
},
templateUrl: 'menu.html',
controller: function($scope, menuSvc) {
$scope.menu = menuSvc.getAll();
},
replace: true
};
});
When content of the menu is updated you may communicate this event to the directive via angular events, so that it will re-read the cotnent fromt he service. See (https://docs.angularjs.org/api/ng/type/$rootScope.Scope) for $on, $emit, $broadcast
Example of a listener at menu controller:
$scope.$on('someEvent', function(){
// update menu content
// trick is that you have to update the menu content, not to overwrite it
// for example like this:
$scope.menu.length = 0;
var newValues = menuSvc.getAll();
newValue.forEach(function(x){ $scope.menu.push(x); } );
});
When Backbone Routers is used for rendering subviews into a main view there is an issue I cannot overcome. Same issue when inner view rendering from inside the home view preferred. This is faced when the page is refreshed. I can describe them in detail as follows.
I have a homeview, whole page. A mainrouter directed me to that
homeview at the beginning. Homeview has tabs. Clicking tabs should
show a subview by a method in homeview navigating by mainrouter.
Subviews are whole width under tab bar. After navigated, url has
been updated. Now subview is shown in the place I wrote. If at this
stage one refreshes the page,this same stage is not reached. Because
of the url, directly the router will route to the method to render
the subview but where to put it in dom. Homeview is not there with
its element.
This also should be solved in case when not routers but inner views
are used to render the subviews from click events inside the
homeview, I mean without routes in the main router to create and
call render of the subviews in the main router. Because tab clicks
updates the url. And When one refreshes the page at this point app
knows no where to go. In my case it sometimes refreshes with no
problem and in some cases do not render anything.
Not updating the url in tab clicks can be a solution, but of course click should visit the hashtag it references. Updating the url is not necessary at all since this is even in desktop browser a single page application. So no place to be bookmark-able, back button-able, as Backbone documentation describes for Backbone.Router.
What are the ways to overcome this issue?
Update
http://www.geekdave.com/2012/04/05/module-specific-subroutes-in-backbone/
var MyApp = {};
MyApp.Router = Backbone.Router.extend({
routes: {
// general routes for cross-app functionality
"" : "showGeneralHomepage",
"cart" : "showShoppingCart",
"account" : "showMyAccount",
// module-specific subroutes:
// invoke the proper module and delegate to the module's
// own SubRoute for handling the rest of the URL
"books/*subroute" : "invokeBooksModule",
"movies/*subroute" : "invokeMoviesModule",
"games/*subroute" : "invokeGamesModule",
"music/*subroute" : "invokeMusicModule"
},
invokeBooksModule: function(subroute) {
if (!MyApp.Routers.Books) {
MyApp.Routers.Books = new MyApp.Books.Router("books/");
}
},
invokeMoviesModule: function(subroute) {
if (!MyApp.Routers.Movies) {
MyApp.Routers.Movies = new MyApp.Movies.Router("movies/");
}
},
invokeGamesModule: function(subroute) {
if (!MyApp.Routers.Games) {
MyApp.Routers.Games = new MyApp.Games.Router("games/");
}
}
});
// Actually initialize
new MyApp.Router();
});
MyApp.Books.Router = Backbone.SubRoute.extend({
routes: {
/* matches http://yourserver.org/books */
"" : "showBookstoreHomepage",
/* matches http://yourserver.org/books/search */
"search" : "searchBooks",
/* matches http://yourserver.org/books/view/:bookId */
"view/:bookId" : "viewBookDetail",
},
showBookstoreHomepage: function() {
// ...module-specific code
},
searchBooks: function() {
// ...module-specific code
},
viewBookDetail: function() {
// ...module-specific code
},
});
[OLD]
There are various ways you can do this, the way I prefer is :
var Router = Backbone.Router.extend({
initialize : function(){
app.homeView = new HomeView({el:"body"}); //I prefer calling it ShellView
},
routes : {
"subView/*":"renderS",
},
renderSubViewOne : function(params){
app.homeView.renderSubView('one',params);
}
});
var HomeView = Backbone.View.extend({
renderSubView:function(viewName, params){
switch(viewName){
case 'one':
var subViewOne = new SubViewOne({el:"tab-one"},params); //_.extend will be cleaner
break;
}
}
});
Above code is just an skeleton to give an idea. If the app is complex, I suggest having multiple routers.
Multiple routers vs single router in BackboneJs
I have a question about the way backbone handles it views.
Suppose I have the following code:
<div id="container">
<div id="header">
</div>
</div>
After this I change header into a backbone view.
How can I now remove that view from the header div again after I'm done with the view and add ANOTHER view to the same div?
I tried just overwriting the variable the view was stored in. This results in the view being changed to the new one...but it will have all the event handlers of the old one still attached to it.
Thanks in advance!
http://documentcloud.github.com/backbone/#View-setElement
This won't automatically remove the original div - you'll want to do that yourself somehow, but then by using setElement you'll have the view's element set to whatever you passed it.. and all of the events will be attached as appropriate. Then you'll need to append that element wherever it is that it needs to go.
--- Let's try this again ----
So, first thing to keep in mind is that views reference DOM elements.. they aren't super tightly bound. So, you can work directly with the jquery object under $el.
var containerView = new ContainerView();
var headerView = new HeaderView();
var anotherHeaderView = new AnotherHeaderView();
containerView.$el.append(headerView.$el);
containerView.$el.append(anotherHeaderView.$el);
anotherHeaderView.$el.detach();
containerView.$el.prepend(anotherHeaderView.$el);
Or you can create methods to control this for you.
var ContainerView = Backbone.View.extend({
addView: function (view) {
var el = view;
if(el.$el) { //so you can pass in both dom and backbone views
el = el.$el;
}
this.$el.append(el);
}
});
Maybe setting the views by view order?
var ContainerView = Backbone.View.extend({
initialize: function () {
this.types = {};
},
addView: function (view, type) {
var el = view;
if(el.$el) { //so you can pass in both dom and backbone views
el = el.$el;
}
this.types[type] = el;
this.resetViews();
},
removeView: function (type) {
delete this.types[type];
this.resetViews();
},
resetViews: function () {
this.$el.children().detach();
_.each(['main_header', 'sub_header', 'sub_sub_header'], function (typekey) {
if(this.types[typekey]) {
this.$el.append(this.types[typekey]);
}
}, this);
}
});
I have been looking at this for quite few hours and I don't think I am able to see the solution.
This is my router.js:
define('router', ['jquery', 'config', 'nav','store'], function ($, config, nav, store) {
var
concepTouch = Sammy('body', function () {
// This says to sammy to use the title plugin
this.use(Sammy.Title);
this.use(Sammy.Mustache);
// Sets the global title prefix
this.setTitle(config.title.prefix);
// So I can access sammy inside private methods
var sammy = this;
function establishRoutes() {
// Defines the main container for content then
var mainConainer = $(config.mainContentContainerId);
// Adds animation loading class to the main container
mainConainer.addClass(config.loadingAnimationCssClass);
// iterates through routes defined in config class then
_.forEach(config.appRoutes, function(obj, key) {
// defines each one as a route
sammy.get(obj.hashV, function(context) {
// Store the requested route as the last viewed route
store.save(config.stateKeys.lastView, context.path);
// Fetches its html template
context.render(obj.tmpltURL, { 'routeData': context.params })
// Appends that htmlo template to the main container and removes loading animation
.then(function(content) {
mainConainer.removeClass(config.loadingAnimationCssClass).html(content);
});
// Finally adds the route title to the prefix
this.title(obj.title);
});
// Overriding sammy's 404
sammy.notFound = function () {
// toast an error about the missing command
toastr.error(sammy.getLocation() + ' Does not exist yet!');
// Go to last visited anf if not
sammy.setLocation(
store.fetch(config.stateKeys.lastView) || config.getDefaultRoute()
);
};
});
}
// Calls for routes to be established
establishRoutes();
}),
// runs concep touch as a sammy App with the initial view of default route
init = function () {
// Try to get today's last visit and if not available then fallback on default
concepTouch.run(store.fetch(config.stateKeys.lastView) || config.getDefaultRoute());
// Make the correct nav item active and add Click handlers for navigation menu
nav.setStartupActiveClass(store.fetch(config.stateKeys.lastView) || sammy.getLocation())
.addActiveClassEventHandlers();
};
return {
init: init,
concepTouch: concepTouch
};
});
This when I submit the search form gets this template for me:
<div id="contacts" class="view animated fadeInLeft">
<h3>Search results for {{routeData}}</h3>
<ul data-bind="template: { name: 'searchresults-template', foreach: searchResults }"></ul>
</div>
<script type="text/html" id="searchresults-template">
<li data-bind="text: type"></li>
</script>
<script>
require(['searchresults'], function (searchresults) {
searchresults.get(to Some how Get routeData.term);
});
</script>
and I can not find the right way to make Mustache pass the data from this line of router.js context.render(obj.tmpltURL, { 'routeData': context.params }) to the {{routeData.term}} inside the template.
{{routeData}} on its own returns `SAMMY.OBJECT: {"TERM": MY SEARCH TERM}`
which I can't navigate to the property i want to from it using . notation. Furthermore even if that worked it can not be passed into Javascript which is what I really need as
searchresults.init(); is waiting for this paramter `searchresults.init(routeData.term);`
Or maybe the answer is to find a way to access sammy's context here? outside of sammy in order to get the params? something like Sammy.Application.context.params['term'] but ofcourse application has no such method so don't know!? :(
Am I going totally the wrong way about it? How Can I easily pass the query string params as accessible objects inside my template so knockout can use it.
Your help is greatly appreciated.
<div id="contacts" class="view animated fadeInLeft">
<h3>Search results for {{#routeData}}{{term}}{{/routeData}}</h3>
<ul data-bind="template: { name: 'searchresults-template', foreach: searchResults }"></ul>
</div>
<script type="text/html" id="searchresults-template">
<li data-bind="text: type"></li>
</script>
<script>
require(['searchresults'], function (searchresults) {
var searchTerm = "{{#routeData}}{{term}}{{/routeData}}";
searchresults.get(searchTerm);
});
</script>