I have the save function of my single page application now up and running, with different front end models and collections in Backbone (a song.js and songsCollection.js), saving to the appropriate backend model in Rails (song.rb). After the user creates a song, comprised of beats and measures, etc....., the backbone routes takes the user to the url containing the song, however, the golbal variable that I used to pass in the all of the songs in the begining of the page start is not getting updated.
How can I call from backbone (either in the routes, or the view), a method or something, to refetch all of the songs from the database, including the recently created song, preferably without changing the Rails side of the URL (prior to the #hash)?
The App.songs variable located within the Assets.js.erb is what I am interested in updated from Rails, after a new song is created.....
I am not opposed to using the gon gem, but if I did, how would I call it to be updated?
Thinking aloud:
Maybe in the assests.js.erb I could have this :
App.updateThis = function(appSongs) {
// then an ajax/pjax call to the Rails songs_controller.rb that returns newAllSongs
appSongs = { songs: newAllSongs }
return appSongs; // this would/should update the global variable
}
Files for reference:
application.js:
require([
'MYAPPLICATION' // this gets passed in as 'Application'
], function(Application){
Application.initialize(App.songs);
});
MYAPPLICATION.js:
define([
'jquery',
'underscore',
'backbone',
'backbone/routers/router', // Request router.js
], function($, _, Backbone, Router){
var initialize = function(options){
window.router = Router.initialize(options);
}
return {
initialize: initialize
};
});
This file is used to package the AssetsPipeline paths to the images and sounds, and pass them to the application when it is rendered, form the gist :
https://gist.github.com/patrickberkeley/3879730
assets.js.erb :
App = {};
App.assets = {
// Returns an object containing all of asset pipeline's image paths.
// This hash is because Rails' Asset Pipeline bundles the routes to files
// per user session, then hands that to the user's session browser, for security.
// So we create in Ruby (erb = embedded ruby) a hash of the images to be accessed
// in the JS.
images: {
<% AssetsUtil.images.each do |img| %>
"<%= img %>" : "<%= asset_path(img) %>",
<% end %>
},
// Return a formatted URL for an asset.
path: function(name) {
// If the file is in our images object, pull the path from there.
if (this.images && this.images[name]) {
return this.images[name];
}
// Otherwise, create a generic asset path.
return '/assets/' + name;
}
};
App.songs = {
songs: <%= Song.all.to_json.html_safe %>
};
routes.js (backbone's route, not rails' route)
define([
.... require.js paths .....
], function($, _, Backbone, mainHomeView, beatSliderView, beatBarsView, componentsView, tempoSliderView, transportView, repButtonView, log, songsCollection, songsViewNew, songsViewIndex, songsViewShow, songsViewEdit){
var AppRouter = Backbone.Router.extend({
songs: {},
routes: {
'new' : 'newSong',
'index' : 'index',
':id/edit' : 'edit',
':id' : 'show',
'.*' : 'newSong'
},
newSong: function(){
var view = new songsViewNew({collection : this.songs});
/// A WHOLE BUNCH OF RENDERING....
},
index: function(){
console.log('bb routes index');
},
show: function(id){
var createdSong = this.songs.get(id);
var view = new songsViewShow(createdSong);
},
edit: function(id){
console.log('bb routes edit');
},
});
// Initialize the Router, with the options, where (options) is declared in MYAPPLCIATION.js
// and called from application.js
//
// (options) == 'assest.js.erb' => App.songs{ songs : <%= Song.all.to_json.html_safe %> }
// (options) == All the songs in the DB
var initialize = function(options){
var app_router = new AppRouter;
app_router.songs = new songsCollection();
app_router.songs.reset(options.songs);
name = '';
$('.component').each( function() {
name = name + $(this).attr('id') + '.';
$(this).children('.measure').each( function() {
name = name + $(this).attr('id') + '.';
$(this).children('.beat').each( function() {
name = name + $(this).attr('id') + '.';
});
});
log.sendLog([[1, "Component structure: "+name]]);
name = '';
});
Backbone.history.start();
return app_router;
};
return {
initialize: initialize
};
});
Using:
rails 3.2.2
backbone.js via gem 'rails-backbone'
require.js via gem 'requirejs-rails'
If I understand your question, you simply need to perform a 'fetch' on the collection after a successful update.
Related
I am creating a little custom plugin for the CKEditor5 for the #neoscms.
Neos is using the #ckeditor5 but with a custom view.
The plugin is more or less a placeholder plugin. The user can configure a data-source with a key value store for items (identifier and labels). The dropdown in the CKEditor is filled with the items and when the user selects an item from the dropdown, it creates a placeholder element that should end in a span element with some data-attributes.
The main idea was to have an empty element and just data-attributes to identify the element and being able to assign live data. But it turns out that the live data thing is tricky. When I manipulate the span with an extra JS snippet on the Website, the CKEditor cannot handle this.
Is it possible to manipulate a view element in the DOM and still have a working Editor?
The Plugin works fine if I just add inner Text in the downCasting and don't replace something. But the live data would be nice.
Neos Backend with a element
Maybe that code gives an idea of the package.
It is not ready yet as this is more or less the main feature ;)
import {Plugin, toWidget, viewToModelPositionOutsideModelElement, Widget,} from "ckeditor5-exports";
import PlaceholderCommand from "./placeHolderCommand";
export default class PlaceholderEditing extends Plugin {
static get requires() {
return [Widget];
}
init() {
this._defineSchema();
this._defineConverters();
this.editor.commands.add(
"placeholder",
new PlaceholderCommand(this.editor)
);
this.editor.editing.mapper.on(
"viewToModelPosition",
viewToModelPositionOutsideModelElement(this.editor.model, (viewElement) =>
viewElement.hasClass("internezzo-placeholder")
)
);
this.editor.config.define("placeholderProps", {
types: ["name", "node", "nodePath"],
});
this.editor.config.define("placeholderBrackets", {
open: "[",
close: "]",
});
}
_defineSchema() {
const schema = this.editor.model.schema;
schema.register("placeholder", {
allowWhere: "$text",
isInline: true,
isObject: true,
allowAttributes: [
"name",
"node",
"nodePath",
"data-placeholder-identifier",
"data-node-identifier",
"data-node-path",
],
});
}
_defineConverters() {
const conversion = this.editor.conversion;
const config = this.editor.config;
conversion.for("upcast").elementToElement({
view: {
name: "span",
classes: ["foobar-placeholder"],
},
model: (viewElement, writer) => {
const name = viewElement.getAttribute('data-placeholder-identifier');
const node = viewElement.getAttribute('data-node-identifier');
const nodePath = viewElement.getAttribute('data-node-path');
const modelWriter = writer.writer || writer;
return modelWriter.createElement("placeholder", {name, node, nodePath, editable: false});
},
});
conversion.for("editingDowncast").elementToElement({
model: "placeholder",
view: (modelItem, writer) => {
const viewWriter = writer.writer || writer;
const widgetElement = createPlaceholderView(modelItem, viewWriter);
return toWidget(widgetElement, viewWriter);
},
});
conversion.for("dataDowncast").elementToElement({
model: "placeholder",
view: (modelItem, writer) => {
const viewWriter = writer.writer || writer;
return createPlaceholderView(modelItem, viewWriter);
},
});
// Helper method for downcast converters.
function createPlaceholderView(modelItem, viewWriter) {
const name = modelItem.getAttribute("name");
const node = modelItem.getAttribute("node");
const nodePath = modelItem.getAttribute("nodePath");
const placeholderView = viewWriter.createContainerElement("span", {
class: "foobar-placeholder",
"data-placeholder-identifier": name,
"data-node-identifier": node,
"data-node-path": nodePath,
});
// Would be nice to remove that and have just empty spans that get dynamic data
let innerText = config.get("placeholderBrackets.open") + name;
innerText += config.get("placeholderBrackets.close");
viewWriter.insert(
viewWriter.createPositionAt(placeholderView, 0),
viewWriter.createText(innerText)
);
return placeholderView;
}
}
}
So, the extra JS snippet that is used by the website is searching for spans with the class foobar-placeholder and writes a value with live data into the span. That works in the frontend, of course, but the backend of the CMS that uses CKEditor has issues with the changing data.
I could not find a solution with docs of CKEditor, and maybe I misuse the API somehow, but I now found a working solution for me.
My website snippet is now communicating with the Plugin via Broadcast messages. And then I search for placeholder elements and check if I need to change an attribute.
const broadcastChannel = new BroadcastChannel('placeholder:changeData');
broadcastChannel.postMessage({identifier: name, value});
And in the plugin
// Receive new values for placeholder via broadcast
const broadcastChannel = new BroadcastChannel('placeholder:changeData');
broadcastChannel.onmessage = (message) => {
const identifier = get('data.identifier', message);
const newValue = get('data.value', message);
this.editor.model.change( writer => {
if (identifier) {
this._replaceAttribute(writer, identifier, newValue);
}
});
};
Only downside now is that I need to reload the page, but already read that this is maybe cause by my element down casting and I change attributes.
I'm working with Laravel 8 + inertiajs. I can create a product with or without an image. But when I try to update a product and upload a new image, the validation looks for the required field even they're already filled.
here is my input field:
<input name="images" type="file" #input="form.images = $event.target.files[0]" />
in my vue:
props: {
product: Object,
categories: Array
},
data() {
return {
form: this.$inertia.form({
name: this.product.name,
category_id: this.product.category_id,
description: this.product.description,
date: this.product.date,
images: this.product.images
})
}
},
methods: {
update() {
this.form.put(this.route('products.update', this.product.id, {
preserveState: true
}))
},
}
})
my update controller:
public function update(UpdateProductRequest $request, Product $product)
{
$inputs = $request->validated();
if ($request->hasFile('images')) {
$filename = $request->images->getClientOriginalName();
$file = $request->images->storeAs(('images'), $filename);
$product->images = $file;
$inputs['images'] = $product->images;
}
$product->name = $inputs['name'];
$product->category_id = $inputs['category_id'];
$product->description = $inputs['description'];
$product->date = $inputs['date'];
$product->update();
session()->flash('flash.banner', 'Product Updated Successfuly');
session()->flash('flash.bannerStyle', 'success');
return redirect()->route('products.index');
}
multipart/form-data request is not natively supported in some languages for the put,patch or delete methods. The workaround here is to simply upload files using post instead.
Some frameworks, such as Laravel and Rails, support form method spoofing, which allows you to upload the files using post, but have the framework handle the request as a put or patch request. This is done by including a _method attribute in the data of your request.
Inertia.post(`/users/${user.id}`, {
_method: 'put',
avatar: form.avatar,
})
A contrived example of bi-directional data binding
var user = {
model: function(name) {
this.name = m.prop(name);
},
controller: function() {
return {user: new user.model("John Doe")};
},
view: function(controller) {
m.render("body", [
m("input", {onchange: m.withAttr("value", controller.user.name), value: controller.user.name()})
]);
}
};
https://lhorie.github.io/mithril/mithril.withAttr.html
I tried the above code does not work nothing.
It was the first to try to append the following.
m.mount(document.body, user);
Uncaught SyntaxError: Unexpected token n
Then I tried to append the following.
var users = m.prop([]);
var error = m.prop("");
m.request({method: "GET", url: "/users/index.php"})
.then(users, error);
▼/users/index.php
<?php
echo '[{name: "John"}, {name: "Mary"}]';
Uncaught SyntaxError: Unexpected token n
How do I operate the m.withAttr tutorials code?
Try returning m('body', [...]) from your controller.
view: function (ctrl) {
return m("body", [
...
]);
}
render should not be used inside of Mithril components (render is only used to mount Mithril components on existing DOM nodes).
The example is difficult to operate because it's contrived, it's not meant to be working out-of-the-box. Here's a slightly modified, working version:
http://jsfiddle.net/ciscoheat/8dwenn02/2/
var user = {
model: function(name) {
this.name = m.prop(name);
},
controller: function() {
return {user: new user.model("John Doe")};
},
view: function(controller) {
return [
m("input", {
oninput: m.withAttr("value", controller.user.name),
value: controller.user.name()
}),
m("h1", controller.user.name())
];
}
};
m.mount(document.body, user);
Changes made:
m.mount injects html inside the element specified as first parameter, so rendering a body element in view will make a body inside a body.
Changed the input field event to oninput for instant feedback, and added a h1 to display the model, so you can see it changing when the input field changes.
Using m.request
Another example how to make an ajax request that displays the retrieved data, as per your modifications:
http://jsfiddle.net/ciscoheat/3senfh9c/
var userList = {
controller: function() {
var users = m.prop([]);
var error = m.prop("");
m.request({
method: "GET",
url: "http://jsonplaceholder.typicode.com/users",
}).then(users, error);
return { users: users, error: error };
},
view: function(controller) {
return [
controller.users().map(function(u) {
return m("div", u.name)
}),
controller.error() ? m(".error", {style: "color:red"}, "Error: " + controller.error()) : null
];
}
};
m.mount(document.body, userList);
The Unexpected token n error can happen if the requested url doesn't return valid JSON, so you need to fix the JSON data in /users/index.php to make it work with your own code. There are no quotes around the name field.
How can we get and post api in Titanium alloy?
I am having the api of userDetails, I just want that how can i code to get the data from api.
function getUserDetails(){
}
Thanks in advance.
As you mentioned, you are using Titanium alloy.
So another approach be to extend the Alloy's Model and Collection ( which are based on backbone.js concept ).
There are already some implementation at RestAPI Sync Adapter also proper description/usage at Titanium RestApi sync.
I also provide the description and methodology used, in-case link gets broken:
Create a Model : Alloy Models are extensions of Backbone.js Models, so when you're defining specific information about your data, you do it by implementing certain methods common to all Backbone Models, therefor overriding the parent methods. Here we will override the url() method of backbone to allow our custom url endpoint.
Path :/app/models/node.js
exports.definition = {
config: {
adapter: {
type: "rest",
collection_name: "node"
}
},
extendCollection: function(Collection) {
_.extend(Collection.prototype, {
url: function() {
return "http://www.example.com/ws/node";
},
});
return Collection;
}
};
Configure a REST sync adapter : The main purpose of a sync adapter is to override Backbone's default sync method with something that fetches your data. In our example, we'll run through a few integrity checks before calling a function to fetch our data using a Ti.Network.createHTTPClient() call. This will create an object that we can attach headers and handlers to and eventually open and send an xml http request to our server so we can then fetch the data and apply it to our collection.
Path :/app/assets/alloy/sync/rest.js (you may have to create alloy/sync folders first)
// Override the Backbone.sync method with our own sync
functionmodule.exports.sync = function (method, model, opts)
{
var methodMap = {
'create': 'POST',
'read': 'GET',
'update': 'PUT',
'delete': 'DELETE'
};
var type = methodMap[method];
var params = _.extend(
{}, opts);
params.type = type;
//set default headers
params.headers = params.headers || {};
// We need to ensure that we have a base url.
if (!params.url)
{
params.url = model.url();
if (!params.url)
{
Ti.API.error("[REST API] ERROR: NO BASE URL");
return;
}
}
//json data transfers
params.headers['Content-Type'] = 'application/json';
switch (method)
{
case 'delete':
case 'create':
case 'update':
throw "Not Implemented";
break;
case 'read':
fetchData(params, function (_response)
{
if (_response.success)
{
var data = JSON.parse(_response.responseText);
params.success(data, _response.responseText);
}
else
{
params.error(JSON.parse(_response.responseText), _response.responseText);
Ti.API.error('[REST API] ERROR: ' + _response.responseText);
}
});
break;
}
};
function fetchData(_options, _callback)
{
var xhr = Ti.Network.createHTTPClient(
{
timeout: 5000
});
//Prepare the request
xhr.open(_options.type, _options.url);
xhr.onload = function (e)
{
_callback(
{
success: true,
responseText: this.responseText || null,
responseData: this.responseData || null
});
};
//Handle error
xhr.onerror = function (e)
{
_callback(
{
'success': false,
'responseText': e.error
});
Ti.API.error('[REST API] fetchData ERROR: ' + xhr.responseText);
};
for (var header in _options.headers)
{
xhr.setRequestHeader(header, _options.headers[header]);
}
if (_options.beforeSend)
{
_options.beforeSend(xhr);
}
xhr.send(_options.data || null);
}
//we need underscore
var _ = require("alloy/underscore")._;
Setup your View for Model-view binding : Titanium has a feature called Model-View binding, which allows you to create repeatable objects in part of a view for each model in a collection. In our example we'll use a TableView element with the dataCollection property set to node, which is the name of our model, and we'll create a TableViewRow element inside. The row based element will magically repeat for every item in the collection.
Path :/app/views/index.xml
<Alloy>
<Collection src="node">
<Window class="container">
<TableView id="nodeTable" dataCollection="node">
<TableViewRow title="{title}" color="black" />
</TableView>
</Window>
</Alloy>
Finally Controller : Binding the Model to the View requires almost no code at the controller level, the only thing we have to do here is load our collection and initiate a fetch command and the data will be ready to be bound to the view.
Path :/app/controllers/index.js
$.index.open();
var node = Alloy.Collections.node;
node.fetch();
Further reading :
Alloy Models
Sync Adapters
Hope it is helpful.
this is the solution for your problem:-
var request = Titanium.Network.createHTTPClient();
var done=false;
request.onload = function() {
try {
if (this.readyState == 4 && !done) {
done=true;
if(this.status===200){
var content = JSON.parse(this.responseText);
}else{
alert('error code' + this.status);
}
}
} catch (err) {
Titanium.API.error(err);
Titanium.UI.createAlertDialog({
message : err,
title : "Remote Server Error"
});
}
};
request.onerror = function(e) {
Ti.API.info(e.error);
};
request.open("POST", "http://test.com");
request.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
request.send({ test: 'test'});
if you don't get your answer please let me know.
Thanks
Is there a way in Durandal to get the path of the current module? I'm building a dashboard inside of a SPA and would like to organize my widgets in the same way that durandal does with "FolderWidgetName" and the folder would contain a controller.js and view.html file. I tried using the getView() method in my controller.js file but could never get it to look in the current folder for the view.
getView(){
return "view"; // looks in the "App" folder
return "./view"; // looks in the "App/durandal" folder
return "/view"; // looks in the root of the website
return "dashboard/widgets/htmlviewer/view" //don't want to hard code the path
}
I don't want to hardcode the path inside of the controller
I don't want to override the viewlocator because the rest of the app still functions as a regular durandal spa that uses standard conventions.
You could use define(['module'], function(module) { ... in order to get a hold on the current module. getView() would than allow you to set a specific view or, like in the example below, dynamically switch between multiple views.
define(['module'], function(module) {
var roles = ['default', 'role1', 'role2'];
var role = ko.observable('default');
var modulePath = module.id.substr(0, module.id.lastIndexOf('/') +1);
var getView = ko.computed(function(){
var roleViewMap = {
'default': modulePath + 'index.html',
role1: modulePath + 'role1.html',
role2: modulePath + 'role2.html'
};
this.role = (role() || 'default');
return roleViewMap[this.role];
});
return {
showCodeUrl: true,
roles: roles,
role: role,
getView: getView,
propertyOne: 'This is a databound property from the root context.',
propertyTwo: 'This property demonstrates that binding contexts flow through composed views.',
moduleJSON: ko.toJSON(module)
};
});
Here's a live example http://dfiddle.github.io/dFiddle-1.2/#/view-composition/getView
You can simply bind your setup view to router.activeRoute.name or .url and that should do what you are looking for. If you are trying to write back to the setup viewmodels property when loading you can do that like below.
If you are using the revealing module you need to define the functions and create a module definition list and return it. Example :
define(['durandal/plugins/router', 'view models/setup'],
function(router, setup) {
var myObservable = ko.observable();
function activate() {
setup.currentViewName = router.activeRoute.name;
return refreshData();
}
var refreshData = function () {
myDataService.getSomeData(myObservable);
};
var viewModel = {
activate: activate,
deactivate: deactivate,
canDeactivate: canDeactivate
};
return viewModel;
});
You can also reveal literals, observables and even functions directly while revealing them -
title: ko.observable(true),
desc: "hey!",
canDeactivate: function() { if (title) ? true : false,
Check out durandal's router page for more info on what is available. Also, heads up Durandal 2.0 is switching up the router.
http://durandaljs.com/documentation/Router/
Add an activate function to your viewmodel as follows:
define([],
function() {
var vm = {
//#region Initialization
activate: activate,
//#endregion
};
return vm;
//#region Internal methods
function activate(context) {
var moduleId = context.routeInfo.moduleId;
var hash = context.routeInfo.hash;
}
//#endregion
});